graphql 2.3.4 → 2.3.6

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  3. data/lib/graphql/analysis/analyzer.rb +89 -0
  4. data/lib/graphql/analysis/field_usage.rb +82 -0
  5. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  6. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  7. data/lib/graphql/analysis/query_complexity.rb +183 -0
  8. data/lib/graphql/analysis/{ast/query_depth.rb → query_depth.rb} +23 -25
  9. data/lib/graphql/analysis/visitor.rb +282 -0
  10. data/lib/graphql/analysis.rb +92 -1
  11. data/lib/graphql/dataloader/async_dataloader.rb +2 -0
  12. data/lib/graphql/dataloader/null_dataloader.rb +1 -1
  13. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +7 -4
  14. data/lib/graphql/execution/interpreter/runtime.rb +40 -59
  15. data/lib/graphql/execution/interpreter.rb +2 -2
  16. data/lib/graphql/language/nodes.rb +17 -22
  17. data/lib/graphql/language/parser.rb +54 -13
  18. data/lib/graphql/query/validation_pipeline.rb +2 -2
  19. data/lib/graphql/query.rb +1 -1
  20. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  21. data/lib/graphql/schema/addition.rb +21 -11
  22. data/lib/graphql/schema/argument.rb +19 -5
  23. data/lib/graphql/schema/directive.rb +2 -0
  24. data/lib/graphql/schema/field.rb +8 -0
  25. data/lib/graphql/schema/has_single_input_argument.rb +1 -0
  26. data/lib/graphql/schema/input_object.rb +1 -0
  27. data/lib/graphql/schema/introspection_system.rb +2 -2
  28. data/lib/graphql/schema/late_bound_type.rb +4 -0
  29. data/lib/graphql/schema/list.rb +2 -2
  30. data/lib/graphql/schema/member/has_arguments.rb +2 -35
  31. data/lib/graphql/schema/member/has_directives.rb +1 -1
  32. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  33. data/lib/graphql/schema/member/type_system_helpers.rb +1 -2
  34. data/lib/graphql/schema/resolver.rb +1 -0
  35. data/lib/graphql/schema/warden.rb +2 -3
  36. data/lib/graphql/schema.rb +20 -20
  37. data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
  38. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  39. data/lib/graphql/subscriptions/broadcast_analyzer.rb +1 -1
  40. data/lib/graphql/subscriptions.rb +1 -1
  41. data/lib/graphql/type_kinds.rb +1 -1
  42. data/lib/graphql/version.rb +1 -1
  43. data/lib/graphql.rb +0 -8
  44. metadata +10 -11
  45. data/lib/graphql/analysis/ast/analyzer.rb +0 -91
  46. data/lib/graphql/analysis/ast/field_usage.rb +0 -84
  47. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  48. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  49. data/lib/graphql/analysis/ast/query_complexity.rb +0 -185
  50. data/lib/graphql/analysis/ast/visitor.rb +0 -284
  51. data/lib/graphql/analysis/ast.rb +0 -94
@@ -65,16 +65,6 @@ module GraphQL
65
65
  "#<#{self.class.name} response=#{@response.inspect}>"
66
66
  end
67
67
 
68
- def tap_or_each(obj_or_array)
69
- if obj_or_array.is_a?(Array)
70
- obj_or_array.each do |item|
71
- yield(item, true)
72
- end
73
- else
74
- yield(obj_or_array, false)
75
- end
76
- end
77
-
78
68
  # This _begins_ the execution. Some deferred work
79
69
  # might be stored up in lazies.
80
70
  # @return [void]
@@ -84,7 +74,8 @@ module GraphQL
84
74
  root_type = schema.root_type_for_operation(root_op_type)
85
75
  runtime_object = root_type.wrap(query.root_value, context)
86
76
  runtime_object = schema.sync_lazy(runtime_object)
87
- @response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false)
77
+ is_eager = root_op_type == "mutation"
78
+ @response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, root_operation.selections, is_eager)
88
79
  st = get_current_runtime_state
89
80
  st.current_result = @response
90
81
 
@@ -93,17 +84,9 @@ module GraphQL
93
84
  @response = nil
94
85
  else
95
86
  call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
96
- gathered_selections = gather_selections(runtime_object, root_type, root_operation.selections)
97
- # This is kind of a hack -- `gathered_selections` is an Array if any of the selections
98
- # require isolation during execution (because of runtime directives). In that case,
99
- # make a new, isolated result hash for writing the result into. (That isolated response
100
- # is eventually merged back into the main response)
101
- #
102
- # Otherwise, `gathered_selections` is a hash of selections which can be
103
- # directly evaluated and the results can be written right into the main response hash.
104
- tap_or_each(gathered_selections) do |selections, is_selection_array|
87
+ each_gathered_selections(@response) do |selections, is_selection_array|
105
88
  if is_selection_array
106
- selection_response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false)
89
+ selection_response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, selections, is_eager)
107
90
  final_response = @response
108
91
  else
109
92
  selection_response = @response
@@ -112,12 +95,10 @@ module GraphQL
112
95
 
113
96
  @dataloader.append_job {
114
97
  evaluate_selections(
115
- root_op_type == "mutation",
116
98
  selections,
117
99
  selection_response,
118
100
  final_response,
119
101
  nil,
120
- nil,
121
102
  )
122
103
  }
123
104
  end
@@ -126,6 +107,17 @@ module GraphQL
126
107
  nil
127
108
  end
128
109
 
110
+ def each_gathered_selections(response_hash)
111
+ gathered_selections = gather_selections(response_hash.graphql_application_value, response_hash.graphql_result_type, response_hash.graphql_selections)
112
+ if gathered_selections.is_a?(Array)
113
+ gathered_selections.each do |item|
114
+ yield(item, true)
115
+ end
116
+ else
117
+ yield(gathered_selections, false)
118
+ end
119
+ end
120
+
129
121
  def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = {})
130
122
  selections.each do |node|
131
123
  # Skip gathering this if the directive says so
@@ -138,7 +130,7 @@ module GraphQL
138
130
  selections = selections_by_name[response_key]
139
131
  # if there was already a selection of this field,
140
132
  # use an array to hold all selections,
141
- # otherise, use the single node to represent the selection
133
+ # otherwise, use the single node to represent the selection
142
134
  if selections
143
135
  # This field was already selected at least once,
144
136
  # add this node to the list of selections
@@ -203,7 +195,7 @@ module GraphQL
203
195
  NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
204
196
 
205
197
  # @return [void]
206
- def evaluate_selections(is_eager_selection, gathered_selections, selections_result, target_result, parent_object, runtime_state) # rubocop:disable Metrics/ParameterLists
198
+ def evaluate_selections(gathered_selections, selections_result, target_result, runtime_state) # rubocop:disable Metrics/ParameterLists
207
199
  runtime_state ||= get_current_runtime_state
208
200
  runtime_state.current_result_name = nil
209
201
  runtime_state.current_result = selections_result
@@ -218,7 +210,7 @@ module GraphQL
218
210
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
219
211
  @dataloader.append_job {
220
212
  evaluate_selection(
221
- result_name, field_ast_nodes_or_ast_node, is_eager_selection, selections_result, parent_object
213
+ result_name, field_ast_nodes_or_ast_node, selections_result
222
214
  )
223
215
  finished_jobs += 1
224
216
  if target_result && finished_jobs == enqueued_jobs
@@ -228,7 +220,7 @@ module GraphQL
228
220
  # Field resolution may pause the fiber,
229
221
  # so it wouldn't get to the `Resolve` call that happens below.
230
222
  # So instead trigger a run from this outer context.
231
- if is_eager_selection
223
+ if selections_result.graphql_is_eager
232
224
  @dataloader.clear_cache
233
225
  @dataloader.run
234
226
  @dataloader.clear_cache
@@ -239,7 +231,7 @@ module GraphQL
239
231
  end
240
232
 
241
233
  # @return [void]
242
- def evaluate_selection(result_name, field_ast_nodes_or_ast_node, is_eager_field, selections_result, parent_object) # rubocop:disable Metrics/ParameterLists
234
+ def evaluate_selection(result_name, field_ast_nodes_or_ast_node, selections_result) # rubocop:disable Metrics/ParameterLists
243
235
  return if selections_result.graphql_dead
244
236
  # As a performance optimization, the hash key will be a `Node` if
245
237
  # there's only one selection of the field. But if there are multiple
@@ -266,28 +258,27 @@ module GraphQL
266
258
  owner_object = field_defn.owner.wrap(owner_object, context)
267
259
  end
268
260
 
269
- return_type = field_defn.type
270
261
  if !field_defn.any_arguments?
271
262
  resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
272
263
  if field_defn.extras.size == 0
273
264
  evaluate_selection_with_resolved_keyword_args(
274
- NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type.non_null?, runtime_state
265
+ NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state
275
266
  )
276
267
  else
277
- evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, runtime_state)
268
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state)
278
269
  end
279
270
  else
280
271
  @query.arguments_cache.dataload_for(ast_node, field_defn, owner_object) do |resolved_arguments|
281
272
  runtime_state = get_current_runtime_state # This might be in a different fiber
282
- evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, runtime_state)
273
+ evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state)
283
274
  end
284
275
  end
285
276
  end
286
277
 
287
- def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, object, is_eager_field, result_name, selection_result, parent_object, return_type, runtime_state) # rubocop:disable Metrics/ParameterLists
278
+ def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state) # rubocop:disable Metrics/ParameterLists
288
279
  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|
289
- return_type_non_null = return_type.non_null?
290
280
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
281
+ return_type_non_null = field_defn.type.non_null?
291
282
  continue_value(resolved_arguments, field_defn, return_type_non_null, ast_node, result_name, selection_result)
292
283
  next
293
284
  end
@@ -326,7 +317,8 @@ module GraphQL
326
317
  # to the keyword args hash _before_ freezing everything.
327
318
  extra_args[:argument_details] = :__arguments_add_self
328
319
  when :parent
329
- extra_args[:parent] = parent_object
320
+ parent_result = selection_result.graphql_parent
321
+ extra_args[:parent] = parent_result&.graphql_application_value&.object
330
322
  else
331
323
  extra_args[extra] = field_defn.fetch_extra(extra, context)
332
324
  end
@@ -337,11 +329,11 @@ module GraphQL
337
329
  resolved_arguments.keyword_arguments
338
330
  end
339
331
 
340
- evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null, runtime_state)
332
+ evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state)
341
333
  end
342
334
  end
343
335
 
344
- def evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null, runtime_state) # rubocop:disable Metrics/ParameterLists
336
+ def evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state) # rubocop:disable Metrics/ParameterLists
345
337
  runtime_state.current_field = field_defn
346
338
  runtime_state.current_arguments = resolved_arguments
347
339
  runtime_state.current_result_name = result_name
@@ -384,7 +376,8 @@ module GraphQL
384
376
  end
385
377
  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|
386
378
  owner_type = selection_result.graphql_result_type
387
- continue_value = continue_value(inner_result, field_defn, return_type_non_null, ast_node, result_name, selection_result)
379
+ return_type = field_defn.type
380
+ continue_value = continue_value(inner_result, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
388
381
  if HALT != continue_value
389
382
  was_scoped = runtime_state.was_authorized_by_scope_items
390
383
  runtime_state.was_authorized_by_scope_items = nil
@@ -395,7 +388,7 @@ module GraphQL
395
388
  # If this field is a root mutation field, immediately resolve
396
389
  # all of its child fields before moving on to the next root mutation field.
397
390
  # (Subselections of this mutation will still be resolved level-by-level.)
398
- if is_eager_field
391
+ if selection_result.graphql_is_eager
399
392
  Interpreter::Resolve.resolve_all([field_result], @dataloader)
400
393
  end
401
394
  end
@@ -607,21 +600,11 @@ module GraphQL
607
600
  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|
608
601
  continue_value = continue_value(inner_object, field, is_non_null, ast_node, result_name, selection_result)
609
602
  if HALT != continue_value
610
- response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null)
603
+ response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false)
611
604
  set_result(selection_result, result_name, response_hash, true, is_non_null)
612
-
613
- gathered_selections = gather_selections(continue_value, current_type, next_selections)
614
- # There are two possibilities for `gathered_selections`:
615
- # 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
616
- # This case is handled below, and the result can be written right into the main `response_hash` above.
617
- # In this case, `gathered_selections` is a hash of selections.
618
- # 2. Some selections of this object have runtime directives that may or may not modify execution.
619
- # That part of the selection is evaluated in an isolated way, writing into a sub-response object which is
620
- # eventually merged into the final response. In this case, `gathered_selections` is an array of things to run in isolation.
621
- # (Technically, it's possible that one of those entries _doesn't_ require isolation.)
622
- tap_or_each(gathered_selections) do |selections, is_selection_array|
605
+ each_gathered_selections(response_hash) do |selections, is_selection_array|
623
606
  if is_selection_array
624
- this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null)
607
+ this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, selections, false)
625
608
  final_result = response_hash
626
609
  else
627
610
  this_result = response_hash
@@ -629,11 +612,9 @@ module GraphQL
629
612
  end
630
613
 
631
614
  evaluate_selections(
632
- false,
633
615
  selections,
634
616
  this_result,
635
617
  final_result,
636
- owner_object.object,
637
618
  runtime_state,
638
619
  )
639
620
  end
@@ -644,7 +625,7 @@ module GraphQL
644
625
  # This is true for objects, unions, and interfaces
645
626
  use_dataloader_job = !inner_type.unwrap.kind.input?
646
627
  inner_type_non_null = inner_type.non_null?
647
- response_list = GraphQLResultArray.new(result_name, current_type, response_list, selection_result, is_non_null)
628
+ response_list = GraphQLResultArray.new(result_name, current_type, owner_object, selection_result, is_non_null, next_selections, false)
648
629
  set_result(selection_result, result_name, response_list, true, is_non_null)
649
630
  idx = nil
650
631
  list_value = begin
@@ -654,10 +635,10 @@ module GraphQL
654
635
  idx += 1
655
636
  if use_dataloader_job
656
637
  @dataloader.append_job do
657
- 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)
638
+ 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)
658
639
  end
659
640
  else
660
- 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)
641
+ 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)
661
642
  end
662
643
  end
663
644
 
@@ -688,7 +669,7 @@ module GraphQL
688
669
  end
689
670
  end
690
671
 
691
- 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
672
+ def 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) # rubocop:disable Metrics/ParameterLists
692
673
  runtime_state.current_result_name = this_idx
693
674
  runtime_state.current_result = response_list
694
675
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
@@ -696,7 +677,7 @@ module GraphQL
696
677
  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|
697
678
  continue_value = continue_value(inner_inner_value, field, inner_type_non_null, ast_node, this_idx, response_list)
698
679
  if HALT != continue_value
699
- 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)
680
+ continue_field(continue_value, owner_type, field, inner_type, ast_node, response_list.graphql_selections, false, owner_object, arguments, this_idx, response_list, was_scoped, runtime_state)
700
681
  end
701
682
  end
702
683
  end
@@ -20,7 +20,7 @@ module GraphQL
20
20
  # @param queries [Array<GraphQL::Query, Hash>]
21
21
  # @param context [Hash]
22
22
  # @param max_complexity [Integer, nil]
23
- # @return [Array<Hash>] One result per query
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
26
  case opts
@@ -40,7 +40,7 @@ module GraphQL
40
40
  lazies_at_depth = Hash.new { |h, k| h[k] = [] }
41
41
  multiplex_analyzers = schema.multiplex_analyzers
42
42
  if multiplex.max_complexity
43
- multiplex_analyzers += [GraphQL::Analysis::AST::MaxQueryComplexity]
43
+ multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity]
44
44
  end
45
45
 
46
46
  schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers)
@@ -274,6 +274,8 @@ module GraphQL
274
274
  ]
275
275
 
276
276
  def generate_initialize
277
+ return if method_defined?(:marshal_load, false) # checking for `:initialize` doesn't work right
278
+
277
279
  scalar_method_names = @scalar_methods
278
280
  # TODO: These probably should be scalar methods, but `types` returns an array
279
281
  [:types, :description].each do |extra_method|
@@ -392,16 +394,6 @@ module GraphQL
392
394
 
393
395
  # A single selection in a GraphQL query.
394
396
  class Field < AbstractNode
395
- scalar_methods :name, :alias
396
- children_methods({
397
- arguments: GraphQL::Language::Nodes::Argument,
398
- selections: GraphQL::Language::Nodes::Field,
399
- directives: GraphQL::Language::Nodes::Directive,
400
- })
401
-
402
- # @!attribute selections
403
- # @return [Array<Nodes::Field>] Selections on this object (or empty array if this is a scalar field)
404
-
405
397
  def initialize(name: nil, arguments: NONE, directives: NONE, selections: NONE, field_alias: nil, line: nil, col: nil, pos: nil, filename: nil, source: nil)
406
398
  @name = name
407
399
  @arguments = arguments || NONE
@@ -428,24 +420,19 @@ module GraphQL
428
420
  @line, @col, @filename, @name, @arguments, @directives, @selections, @alias = values
429
421
  end
430
422
 
431
- # Override this because default is `:fields`
432
- self.children_method_name = :selections
433
- end
434
-
435
- # A reusable fragment, defined at document-level.
436
- class FragmentDefinition < AbstractNode
437
- scalar_methods :name, :type
423
+ scalar_methods :name, :alias
438
424
  children_methods({
425
+ arguments: GraphQL::Language::Nodes::Argument,
439
426
  selections: GraphQL::Language::Nodes::Field,
440
427
  directives: GraphQL::Language::Nodes::Directive,
441
428
  })
442
429
 
443
- self.children_method_name = :definitions
444
- # @!attribute name
445
- # @return [String] the identifier for this fragment, which may be applied with `...#{name}`
430
+ # Override this because default is `:fields`
431
+ self.children_method_name = :selections
432
+ end
446
433
 
447
- # @!attribute type
448
- # @return [String] the type condition for this fragment (name of type which it may apply to)
434
+ # A reusable fragment, defined at document-level.
435
+ class FragmentDefinition < AbstractNode
449
436
  def initialize(name: nil, type: nil, directives: NONE, selections: NONE, filename: nil, pos: nil, source: nil, line: nil, col: nil)
450
437
  @name = name
451
438
  @type = type
@@ -469,6 +456,14 @@ module GraphQL
469
456
  def marshal_load(values)
470
457
  @line, @col, @filename, @name, @type, @directives, @selections = values
471
458
  end
459
+
460
+ scalar_methods :name, :type
461
+ children_methods({
462
+ selections: GraphQL::Language::Nodes::Field,
463
+ directives: GraphQL::Language::Nodes::Directive,
464
+ })
465
+
466
+ self.children_method_name = :definitions
472
467
  end
473
468
 
474
469
  # Application of a named fragment in a selection
@@ -379,7 +379,12 @@ module GraphQL
379
379
  v_loc = pos
380
380
  description = if at?(:STRING); string_value; end
381
381
  defn_loc = pos
382
- enum_value = parse_enum_name
382
+ # Any identifier, but not true, false, or null
383
+ enum_value = if at?(:TRUE) || at?(:FALSE) || at?(:NULL)
384
+ expect_token(:IDENTIFIER)
385
+ else
386
+ parse_name
387
+ end
383
388
  v_directives = parse_directives
384
389
  list << EnumValueDefinition.new(pos: v_loc, definition_pos: defn_loc, description: description, name: enum_value, directives: v_directives, filename: @filename, source: self)
385
390
  end
@@ -615,9 +620,6 @@ module GraphQL
615
620
  when :ON
616
621
  advance_token
617
622
  "on"
618
- when :DIRECTIVE
619
- advance_token
620
- "directive"
621
623
  when :EXTEND
622
624
  advance_token
623
625
  "extend"
@@ -634,15 +636,6 @@ module GraphQL
634
636
  end
635
637
  end
636
638
 
637
- # Any identifier, but not true, false, or null
638
- def parse_enum_name
639
- if at?(:TRUE) || at?(:FALSE) || at?(:NULL)
640
- expect_token(:IDENTIFIER)
641
- else
642
- parse_name
643
- end
644
- end
645
-
646
639
  def parse_type_name
647
640
  TypeName.new(pos: pos, name: parse_name, filename: @filename, source: self)
648
641
  end
@@ -733,6 +726,54 @@ module GraphQL
733
726
  loc = pos
734
727
  advance_token
735
728
  VariableIdentifier.new(pos: loc, name: parse_name, filename: @filename, source: self)
729
+ when :SCHEMA
730
+ advance_token
731
+ Nodes::Enum.new(pos: pos, name: "schema", filename: @filename, source: self)
732
+ when :SCALAR
733
+ advance_token
734
+ Nodes::Enum.new(pos: pos, name: "scalar", filename: @filename, source: self)
735
+ when :IMPLEMENTS
736
+ advance_token
737
+ Nodes::Enum.new(pos: pos, name: "implements", filename: @filename, source: self)
738
+ when :INTERFACE
739
+ advance_token
740
+ Nodes::Enum.new(pos: pos, name: "interface", filename: @filename, source: self)
741
+ when :UNION
742
+ advance_token
743
+ Nodes::Enum.new(pos: pos, name: "union", filename: @filename, source: self)
744
+ when :ENUM
745
+ advance_token
746
+ Nodes::Enum.new(pos: pos, name: "enum", filename: @filename, source: self)
747
+ when :INPUT
748
+ advance_token
749
+ Nodes::Enum.new(pos: pos, name: "input", filename: @filename, source: self)
750
+ when :DIRECTIVE
751
+ advance_token
752
+ Nodes::Enum.new(pos: pos, name: "directive", filename: @filename, source: self)
753
+ when :TYPE
754
+ advance_token
755
+ Nodes::Enum.new(pos: pos, name: "type", filename: @filename, source: self)
756
+ when :QUERY
757
+ advance_token
758
+ Nodes::Enum.new(pos: pos, name: "query", filename: @filename, source: self)
759
+ when :MUTATION
760
+ advance_token
761
+ Nodes::Enum.new(pos: pos, name: "mutation", filename: @filename, source: self)
762
+ when :SUBSCRIPTION
763
+ advance_token
764
+ Nodes::Enum.new(pos: pos, name: "subscription", filename: @filename, source: self)
765
+ when :FRAGMENT
766
+ advance_token
767
+ Nodes::Enum.new(pos: pos, name: "fragment", filename: @filename, source: self)
768
+ when :REPEATABLE
769
+ advance_token
770
+ Nodes::Enum.new(pos: pos, name: "repeatable", filename: @filename, source: self)
771
+ when :ON
772
+ advance_token
773
+ Nodes::Enum.new(pos: pos, name: "on", filename: @filename, source: self)
774
+ when :EXTEND
775
+ advance_token
776
+ Nodes::Enum.new(pos: pos, name: "extend", filename: @filename, source: self)
736
777
  else
737
778
  expect_token(:VALUE)
738
779
  end
@@ -100,10 +100,10 @@ module GraphQL
100
100
  # Depending on the analysis engine, we must use different analyzers
101
101
  # remove this once everything has switched over to AST analyzers
102
102
  if max_depth
103
- qa << GraphQL::Analysis::AST::MaxQueryDepth
103
+ qa << GraphQL::Analysis::MaxQueryDepth
104
104
  end
105
105
  if max_complexity
106
- qa << GraphQL::Analysis::AST::MaxQueryComplexity
106
+ qa << GraphQL::Analysis::MaxQueryComplexity
107
107
  end
108
108
  qa
109
109
  else
data/lib/graphql/query.rb CHANGED
@@ -222,7 +222,7 @@ module GraphQL
222
222
  end
223
223
 
224
224
  # Get the result for this query, executing it once
225
- # @return [Hash] A GraphQL response, with `"data"` and/or `"errors"` keys
225
+ # @return [GraphQL::Query::Result] A Hash-like GraphQL response, with `"data"` and/or `"errors"` keys
226
226
  def result
227
227
  if !@executed
228
228
  Execution::Interpreter.run_all(@schema, [self], context: @context)
@@ -9,7 +9,7 @@ module GraphQL
9
9
 
10
10
  # Return the source of `send_node`, but without the keyword argument represented by `pair_node`
11
11
  def source_without_keyword_argument(send_node, pair_node)
12
- # work back to the preceeding comma
12
+ # work back to the preceding comma
13
13
  first_pos = pair_node.location.expression.begin_pos
14
14
  end_pos = pair_node.location.expression.end_pos
15
15
  node_source = send_node.source_range.source
@@ -12,7 +12,7 @@ module GraphQL
12
12
  @possible_types = {}
13
13
  @types = {}
14
14
  @union_memberships = {}
15
- @references = Hash.new { |h, k| h[k] = [] }
15
+ @references = Hash.new { |h, k| h[k] = Set.new }
16
16
  @arguments_with_default_values = []
17
17
  add_type_and_traverse(new_types)
18
18
  end
@@ -20,7 +20,7 @@ module GraphQL
20
20
  private
21
21
 
22
22
  def references_to(thing, from:)
23
- @references[thing] << from
23
+ @references[thing].add(from)
24
24
  end
25
25
 
26
26
  def get_type(name)
@@ -95,7 +95,7 @@ module GraphQL
95
95
  # It's a union with possible_types
96
96
  # Replace the item by class name
97
97
  owner.assign_type_membership_object_type(type)
98
- @possible_types[owner.graphql_name] = owner.possible_types
98
+ @possible_types[owner] = owner.possible_types
99
99
  elsif type.kind.interface? && (owner.kind.object? || owner.kind.interface?)
100
100
  new_interfaces = []
101
101
  owner.interfaces.each do |int_t|
@@ -110,7 +110,7 @@ module GraphQL
110
110
  end
111
111
  owner.implements(*new_interfaces)
112
112
  new_interfaces.each do |int|
113
- pt = @possible_types[int.graphql_name] ||= []
113
+ pt = @possible_types[int] ||= []
114
114
  if !pt.include?(owner) && owner.is_a?(Class)
115
115
  pt << owner
116
116
  end
@@ -126,6 +126,7 @@ module GraphQL
126
126
  @types[type.graphql_name] = type
127
127
  when GraphQL::Schema::Field, GraphQL::Schema::Argument
128
128
  orig_type = owner.type
129
+ unwrapped_t = type
129
130
  # Apply list/non-null wrapper as needed
130
131
  if orig_type.respond_to?(:of_type)
131
132
  transforms = []
@@ -142,6 +143,7 @@ module GraphQL
142
143
  transforms.reverse_each { |t| type = type.public_send(t) }
143
144
  end
144
145
  owner.type = type
146
+ references_to(unwrapped_t, from: owner)
145
147
  else
146
148
  raise "Unexpected update: #{owner.inspect} #{type.inspect}"
147
149
  end
@@ -164,7 +166,9 @@ module GraphQL
164
166
  @directives << type
165
167
  type.all_argument_definitions.each do |arg|
166
168
  arg_type = arg.type.unwrap
167
- references_to(arg_type, from: arg)
169
+ if !arg_type.is_a?(GraphQL::Schema::LateBoundType)
170
+ references_to(arg_type, from: arg)
171
+ end
168
172
  path.push(arg.graphql_name)
169
173
  add_type(arg_type, owner: arg, late_types: late_types, path: path)
170
174
  path.pop
@@ -187,14 +191,18 @@ module GraphQL
187
191
  type.all_field_definitions.each do |field|
188
192
  name = field.graphql_name
189
193
  field_type = field.type.unwrap
190
- references_to(field_type, from: field)
194
+ if !field_type.is_a?(GraphQL::Schema::LateBoundType)
195
+ references_to(field_type, from: field)
196
+ end
191
197
  path.push(name)
192
198
  add_type(field_type, owner: field, late_types: late_types, path: path)
193
199
  add_directives_from(field)
194
200
  field.all_argument_definitions.each do |arg|
195
201
  add_directives_from(arg)
196
202
  arg_type = arg.type.unwrap
197
- references_to(arg_type, from: arg)
203
+ if !arg_type.is_a?(GraphQL::Schema::LateBoundType)
204
+ references_to(arg_type, from: arg)
205
+ end
198
206
  path.push(arg.graphql_name)
199
207
  add_type(arg_type, owner: arg, late_types: late_types, path: path)
200
208
  path.pop
@@ -209,7 +217,9 @@ module GraphQL
209
217
  type.all_argument_definitions.each do |arg|
210
218
  add_directives_from(arg)
211
219
  arg_type = arg.type.unwrap
212
- references_to(arg_type, from: arg)
220
+ if !arg_type.is_a?(GraphQL::Schema::LateBoundType)
221
+ references_to(arg_type, from: arg)
222
+ end
213
223
  path.push(arg.graphql_name)
214
224
  add_type(arg_type, owner: arg, late_types: late_types, path: path)
215
225
  path.pop
@@ -219,7 +229,7 @@ module GraphQL
219
229
  end
220
230
  end
221
231
  if type.kind.union?
222
- @possible_types[type.graphql_name] = type.all_possible_types
232
+ @possible_types[type] = type.all_possible_types
223
233
  path.push("possible_types")
224
234
  type.all_possible_types.each do |t|
225
235
  add_type(t, owner: type, late_types: late_types, path: path)
@@ -234,7 +244,7 @@ module GraphQL
234
244
  path.pop
235
245
  end
236
246
  if type.kind.object?
237
- possible_types_for_this_name = @possible_types[type.graphql_name] ||= []
247
+ possible_types_for_this_name = @possible_types[type] ||= []
238
248
  possible_types_for_this_name << type
239
249
  end
240
250
 
@@ -246,7 +256,7 @@ module GraphQL
246
256
  interface_type = interface_type_membership.abstract_type
247
257
  # We can get these now; we'll have to get late-bound types later
248
258
  if interface_type.is_a?(Module) && type.is_a?(Class)
249
- implementers = @possible_types[interface_type.graphql_name] ||= []
259
+ implementers = @possible_types[interface_type] ||= []
250
260
  implementers << type
251
261
  end
252
262
  when String, Schema::LateBoundType
@@ -312,10 +312,15 @@ module GraphQL
312
312
  context.query.after_lazy(custom_loaded_value) do |custom_value|
313
313
  if loads
314
314
  if type.list?
315
- loaded_values = custom_value.each_with_index.map { |custom_val, idx|
316
- id = coerced_value[idx]
317
- load_method_owner.authorize_application_object(self, id, context, custom_val)
318
- }
315
+ loaded_values = []
316
+ context.dataloader.run_isolated do
317
+ custom_value.each_with_index.map { |custom_val, idx|
318
+ id = coerced_value[idx]
319
+ context.dataloader.append_job do
320
+ loaded_values[idx] = load_method_owner.authorize_application_object(self, id, context, custom_val)
321
+ end
322
+ }
323
+ end
319
324
  context.schema.after_any_lazies(loaded_values, &:itself)
320
325
  else
321
326
  load_method_owner.authorize_application_object(self, coerced_value, context, custom_loaded_value)
@@ -326,7 +331,16 @@ module GraphQL
326
331
  end
327
332
  elsif loads
328
333
  if type.list?
329
- loaded_values = coerced_value.map { |val| load_method_owner.load_and_authorize_application_object(self, val, context) }
334
+ loaded_values = []
335
+ # We want to run these list items all together,
336
+ # but we also need to wait for the result so we can return it :S
337
+ context.dataloader.run_isolated do
338
+ coerced_value.each_with_index { |val, idx|
339
+ context.dataloader.append_job do
340
+ loaded_values[idx] = load_method_owner.load_and_authorize_application_object(self, val, context)
341
+ end
342
+ }
343
+ end
330
344
  context.schema.after_any_lazies(loaded_values, &:itself)
331
345
  else
332
346
  load_method_owner.load_and_authorize_application_object(self, coerced_value, context)
@@ -188,6 +188,8 @@ module GraphQL
188
188
  assert_has_location(SCALAR)
189
189
  elsif @owner < GraphQL::Schema
190
190
  assert_has_location(SCHEMA)
191
+ elsif @owner < GraphQL::Schema::Resolver
192
+ assert_has_location(FIELD_DEFINITION)
191
193
  else
192
194
  raise "Unexpected directive owner class: #{@owner}"
193
195
  end