graphql 2.5.11 → 2.6.10

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 (154) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/field_extractor.rb +17 -1
  4. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  5. data/lib/generators/graphql/templates/schema.erb +2 -1
  6. data/lib/graphql/analysis/query_complexity.rb +31 -15
  7. data/lib/graphql/analysis.rb +20 -13
  8. data/lib/graphql/backtrace/table.rb +10 -1
  9. data/lib/graphql/current.rb +7 -1
  10. data/lib/graphql/dashboard/application_controller.rb +45 -0
  11. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  12. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  13. data/lib/graphql/dashboard/subscriptions.rb +2 -1
  14. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
  15. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
  16. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
  17. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
  18. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
  19. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
  20. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
  21. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
  22. data/lib/graphql/dashboard.rb +14 -73
  23. data/lib/graphql/dataloader/async_dataloader.rb +336 -60
  24. data/lib/graphql/dataloader/null_dataloader.rb +48 -10
  25. data/lib/graphql/dataloader/source.rb +32 -19
  26. data/lib/graphql/dataloader.rb +107 -39
  27. data/lib/graphql/date_encoding_error.rb +1 -1
  28. data/lib/graphql/execution/directive_checks.rb +2 -0
  29. data/lib/graphql/execution/field_resolve_step.rb +804 -0
  30. data/lib/graphql/execution/finalize.rb +229 -0
  31. data/lib/graphql/execution/input_values.rb +333 -0
  32. data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -0
  33. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  34. data/lib/graphql/execution/interpreter/resolve.rb +7 -13
  35. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
  36. data/lib/graphql/execution/interpreter/runtime.rb +60 -40
  37. data/lib/graphql/execution/interpreter.rb +17 -23
  38. data/lib/graphql/execution/lazy.rb +1 -1
  39. data/lib/graphql/execution/load_argument_step.rb +102 -0
  40. data/lib/graphql/execution/multiplex.rb +1 -1
  41. data/lib/graphql/execution/next.rb +98 -0
  42. data/lib/graphql/execution/prepare_object_step.rb +151 -0
  43. data/lib/graphql/execution/resolve_type_step.rb +27 -0
  44. data/lib/graphql/execution/runner.rb +457 -0
  45. data/lib/graphql/execution/selections_step.rb +95 -0
  46. data/lib/graphql/execution.rb +15 -4
  47. data/lib/graphql/execution_error.rb +17 -12
  48. data/lib/graphql/float_decoding_error.rb +13 -0
  49. data/lib/graphql/float_encoding_error.rb +28 -0
  50. data/lib/graphql/introspection/directive_type.rb +7 -3
  51. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  52. data/lib/graphql/introspection/entry_points.rb +11 -3
  53. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  54. data/lib/graphql/introspection/field_type.rb +13 -5
  55. data/lib/graphql/introspection/input_value_type.rb +21 -13
  56. data/lib/graphql/introspection/schema_type.rb +6 -2
  57. data/lib/graphql/introspection/type_type.rb +64 -28
  58. data/lib/graphql/invalid_null_error.rb +11 -5
  59. data/lib/graphql/language/block_string.rb +6 -11
  60. data/lib/graphql/language/cache.rb +84 -14
  61. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  62. data/lib/graphql/language/lexer.rb +27 -17
  63. data/lib/graphql/language/nodes.rb +2 -1
  64. data/lib/graphql/language/parser.rb +1 -1
  65. data/lib/graphql/language.rb +32 -17
  66. data/lib/graphql/pagination/connection.rb +2 -0
  67. data/lib/graphql/pagination/connections.rb +30 -0
  68. data/lib/graphql/pagination/relation_connection.rb +10 -1
  69. data/lib/graphql/query/context.rb +10 -3
  70. data/lib/graphql/query/null_context.rb +9 -3
  71. data/lib/graphql/query/partial.rb +18 -3
  72. data/lib/graphql/query/variable_validation_error.rb +16 -1
  73. data/lib/graphql/query/variables.rb +1 -1
  74. data/lib/graphql/query.rb +12 -3
  75. data/lib/graphql/railtie.rb +2 -1
  76. data/lib/graphql/runtime_error.rb +6 -0
  77. data/lib/graphql/schema/argument.rb +13 -1
  78. data/lib/graphql/schema/build_from_definition.rb +28 -1
  79. data/lib/graphql/schema/directive/feature.rb +4 -0
  80. data/lib/graphql/schema/directive/transform.rb +20 -0
  81. data/lib/graphql/schema/directive.rb +48 -13
  82. data/lib/graphql/schema/field/connection_extension.rb +4 -37
  83. data/lib/graphql/schema/field/scope_extension.rb +18 -13
  84. data/lib/graphql/schema/field.rb +79 -48
  85. data/lib/graphql/schema/field_extension.rb +11 -8
  86. data/lib/graphql/schema/has_single_input_argument.rb +24 -13
  87. data/lib/graphql/schema/input_object.rb +6 -2
  88. data/lib/graphql/schema/interface.rb +26 -0
  89. data/lib/graphql/schema/introspection_system.rb +6 -21
  90. data/lib/graphql/schema/list.rb +5 -1
  91. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -11
  92. data/lib/graphql/schema/member/has_arguments.rb +43 -14
  93. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  94. data/lib/graphql/schema/member/has_dataloader.rb +37 -0
  95. data/lib/graphql/schema/member/has_fields.rb +86 -5
  96. data/lib/graphql/schema/member.rb +5 -0
  97. data/lib/graphql/schema/non_null.rb +1 -1
  98. data/lib/graphql/schema/object.rb +1 -0
  99. data/lib/graphql/schema/printer.rb +1 -1
  100. data/lib/graphql/schema/ractor_shareable.rb +1 -0
  101. data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
  102. data/lib/graphql/schema/resolver.rb +76 -1
  103. data/lib/graphql/schema/subscription.rb +53 -10
  104. data/lib/graphql/schema/timeout.rb +2 -2
  105. data/lib/graphql/schema/validator/allow_blank_validator.rb +3 -3
  106. data/lib/graphql/schema/validator/allow_null_validator.rb +3 -3
  107. data/lib/graphql/schema/validator/exclusion_validator.rb +2 -2
  108. data/lib/graphql/schema/validator/format_validator.rb +3 -3
  109. data/lib/graphql/schema/validator/inclusion_validator.rb +2 -2
  110. data/lib/graphql/schema/validator/length_validator.rb +6 -6
  111. data/lib/graphql/schema/validator/numericality_validator.rb +19 -19
  112. data/lib/graphql/schema/validator/required_validator.rb +38 -5
  113. data/lib/graphql/schema/validator.rb +9 -0
  114. data/lib/graphql/schema/visibility/profile.rb +74 -53
  115. data/lib/graphql/schema/visibility/visit.rb +1 -1
  116. data/lib/graphql/schema/visibility.rb +32 -24
  117. data/lib/graphql/schema/wrapper.rb +11 -1
  118. data/lib/graphql/schema.rb +93 -27
  119. data/lib/graphql/static_validation/base_visitor.rb +90 -66
  120. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  121. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  122. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
  123. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
  124. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
  125. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -4
  126. data/lib/graphql/static_validation/rules/fields_will_merge.rb +351 -248
  127. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
  128. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  129. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
  130. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
  131. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
  132. data/lib/graphql/static_validation/validation_context.rb +1 -1
  133. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  134. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +40 -10
  135. data/lib/graphql/subscriptions/serialize.rb +10 -4
  136. data/lib/graphql/subscriptions.rb +36 -1
  137. data/lib/graphql/testing/helpers.rb +12 -9
  138. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  139. data/lib/graphql/testing.rb +1 -0
  140. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  141. data/lib/graphql/tracing/detailed_trace.rb +70 -7
  142. data/lib/graphql/tracing/perfetto_trace.rb +213 -81
  143. data/lib/graphql/tracing/sentry_trace.rb +3 -1
  144. data/lib/graphql/tracing/trace.rb +6 -0
  145. data/lib/graphql/types/float.rb +18 -4
  146. data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
  147. data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
  148. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  149. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  150. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  151. data/lib/graphql/unauthorized_error.rb +9 -1
  152. data/lib/graphql/version.rb +1 -1
  153. data/lib/graphql.rb +8 -2
  154. metadata +23 -59
@@ -35,11 +35,10 @@ module GraphQL
35
35
  # @return [GraphQL::Query::Context]
36
36
  attr_reader :context
37
37
 
38
- def initialize(query:, lazies_at_depth:)
38
+ def initialize(query:)
39
39
  @query = query
40
40
  @current_trace = query.current_trace
41
41
  @dataloader = query.multiplex.dataloader
42
- @lazies_at_depth = lazies_at_depth
43
42
  @schema = query.schema
44
43
  @context = query.context
45
44
  @response = nil
@@ -199,7 +198,7 @@ module GraphQL
199
198
 
200
199
  def each_gathered_selections(response_hash)
201
200
  ordered_result_keys = []
202
- gathered_selections = gather_selections(response_hash.graphql_application_value, response_hash.graphql_result_type, response_hash.graphql_selections, nil, {}, ordered_result_keys)
201
+ gathered_selections = gather_selections(response_hash, response_hash.graphql_application_value, response_hash.graphql_result_type, response_hash.graphql_selections, nil, {}, ordered_result_keys)
203
202
  ordered_result_keys.uniq!
204
203
  if gathered_selections.is_a?(Array)
205
204
  gathered_selections.each do |item|
@@ -210,15 +209,13 @@ module GraphQL
210
209
  end
211
210
  end
212
211
 
213
- def gather_selections(owner_object, owner_type, selections, selections_to_run, selections_by_name, ordered_result_keys)
212
+ def gather_selections(graphql_response, owner_object, owner_type, selections, selections_to_run, selections_by_name, ordered_result_keys)
214
213
  selections.each do |node|
215
- # Skip gathering this if the directive says so
216
- if !directives_include?(node, owner_object, owner_type)
217
- next
218
- end
219
-
220
214
  if node.is_a?(GraphQL::Language::Nodes::Field)
221
215
  response_key = node.alias || node.name
216
+ if !directives_include?(node, owner_object, owner_type, graphql_response, response_key)
217
+ next
218
+ end
222
219
  ordered_result_keys << response_key
223
220
  selections = selections_by_name[response_key]
224
221
  # if there was already a selection of this field,
@@ -235,6 +232,9 @@ module GraphQL
235
232
  selections_by_name[response_key] = node
236
233
  end
237
234
  else
235
+ if !directives_include?(node, owner_object, owner_type, graphql_response, nil)
236
+ next
237
+ end
238
238
  # This is an InlineFragment or a FragmentSpread
239
239
  if !@runtime_directive_names.empty? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
240
240
  next_selections = {}
@@ -256,14 +256,14 @@ module GraphQL
256
256
  type_defn = query.types.type(node.type.name)
257
257
 
258
258
  if query.types.possible_types(type_defn).include?(owner_type)
259
- result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
259
+ result = gather_selections(graphql_response, owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
260
260
  if !result.equal?(next_selections)
261
261
  selections_to_run = result
262
262
  end
263
263
  end
264
264
  else
265
265
  # it's an untyped fragment, definitely continue
266
- result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
266
+ result = gather_selections(graphql_response, owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
267
267
  if !result.equal?(next_selections)
268
268
  selections_to_run = result
269
269
  end
@@ -272,7 +272,7 @@ module GraphQL
272
272
  fragment_def = query.fragments[node.name]
273
273
  type_defn = query.types.type(fragment_def.type.name)
274
274
  if query.types.possible_types(type_defn).include?(owner_type)
275
- result = gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections, ordered_result_keys)
275
+ result = gather_selections(graphql_response, owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections, ordered_result_keys)
276
276
  if !result.equal?(next_selections)
277
277
  selections_to_run = result
278
278
  end
@@ -340,7 +340,7 @@ module GraphQL
340
340
  end
341
341
  field_name = ast_node.name
342
342
  owner_type = selections_result.graphql_result_type
343
- field_defn = query.types.field(owner_type, field_name)
343
+ field_defn = query.types.field(owner_type, field_name) || raise(GraphQL::Error, "No field definition found for #{owner_type.graphql_name}.#{field_name} (at #{ast_node.position})")
344
344
 
345
345
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
346
346
  runtime_state = get_current_runtime_state
@@ -365,6 +365,10 @@ module GraphQL
365
365
  else
366
366
  @query.arguments_cache.dataload_for(ast_node, field_defn, owner_object) do |resolved_arguments|
367
367
  runtime_state = get_current_runtime_state # This might be in a different fiber
368
+ runtime_state.current_field = field_defn
369
+ runtime_state.current_arguments = resolved_arguments
370
+ runtime_state.current_result_name = result_name
371
+ runtime_state.current_result = selections_result
368
372
  evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state)
369
373
  end
370
374
  end
@@ -373,6 +377,8 @@ module GraphQL
373
377
  def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state) # rubocop:disable Metrics/ParameterLists
374
378
  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|
375
379
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
380
+ next if selection_result.collect_result(result_name, resolved_arguments)
381
+
376
382
  return_type_non_null = field_defn.type.non_null?
377
383
  continue_value(resolved_arguments, field_defn, return_type_non_null, ast_node, result_name, selection_result)
378
384
  next
@@ -446,7 +452,7 @@ module GraphQL
446
452
  }
447
453
  end
448
454
 
449
- field_result = call_method_on_directives(:resolve, object, directives) do
455
+ call_method_on_directives(:resolve, object, directives) do
450
456
  if !directives.empty?
451
457
  # This might be executed in a different context; reset this info
452
458
  runtime_state = get_current_runtime_state
@@ -472,6 +478,8 @@ module GraphQL
472
478
  end
473
479
  @current_trace.end_execute_field(field_defn, object, kwarg_arguments, query, app_result)
474
480
  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|
481
+ next if selection_result.collect_result(result_name, inner_result)
482
+
475
483
  owner_type = selection_result.graphql_result_type
476
484
  return_type = field_defn.type
477
485
  continue_value = continue_value(inner_result, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
@@ -484,12 +492,6 @@ module GraphQL
484
492
  end
485
493
  end
486
494
  end
487
- # If this field is a root mutation field, immediately resolve
488
- # all of its child fields before moving on to the next root mutation field.
489
- # (Subselections of this mutation will still be resolved level-by-level.)
490
- if selection_result.graphql_is_eager
491
- Interpreter::Resolve.resolve_all([field_result], @dataloader)
492
- end
493
495
  end
494
496
 
495
497
  def set_result(selection_result, result_name, value, is_child_result, is_non_null)
@@ -572,7 +574,7 @@ module GraphQL
572
574
  value.path ||= current_path
573
575
  value.ast_node ||= ast_node
574
576
  context.errors << value
575
- if selection_result
577
+ if selection_result && result_name
576
578
  set_result(selection_result, result_name, nil, false, is_non_null)
577
579
  end
578
580
  end
@@ -596,7 +598,7 @@ module GraphQL
596
598
  err
597
599
  end
598
600
  continue_value(next_value, field, is_non_null, ast_node, result_name, selection_result)
599
- elsif GraphQL::Execution::SKIP == value
601
+ elsif value.is_a?(GraphQL::Execution::Skip)
600
602
  # It's possible a lazy was already written here
601
603
  case selection_result
602
604
  when GraphQLResultHash
@@ -667,7 +669,11 @@ module GraphQL
667
669
  rescue GraphQL::ExecutionError => ex_err
668
670
  return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
669
671
  rescue StandardError => err
670
- query.handle_or_reraise(err)
672
+ begin
673
+ query.handle_or_reraise(err)
674
+ rescue GraphQL::ExecutionError => ex_err
675
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
676
+ end
671
677
  end
672
678
  set_result(selection_result, result_name, r, false, is_non_null)
673
679
  r
@@ -750,7 +756,7 @@ module GraphQL
750
756
  idx += 1
751
757
  if use_dataloader_job
752
758
  @dataloader.append_job do
753
- 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)
759
+ 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, nil)
754
760
  end
755
761
  else
756
762
  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)
@@ -759,8 +765,7 @@ module GraphQL
759
765
 
760
766
  response_list
761
767
  rescue NoMethodError => err
762
- # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
763
- if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
768
+ if err.name == :each && err.receiver == value
764
769
  # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
765
770
  raise ListResultFailedError.new(value: value, field: field, path: current_path)
766
771
  else
@@ -792,6 +797,7 @@ module GraphQL
792
797
  end
793
798
 
794
799
  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
800
+ runtime_state ||= get_current_runtime_state
795
801
  runtime_state.current_result_name = this_idx
796
802
  runtime_state.current_result = response_list
797
803
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
@@ -844,11 +850,31 @@ module GraphQL
844
850
  end
845
851
 
846
852
  # Check {Schema::Directive.include?} for each directive that's present
847
- def directives_include?(node, graphql_object, parent_type)
853
+ def directives_include?(node, graphql_object, parent_type, selection_result, extra_path_part)
848
854
  node.directives.each do |dir_node|
849
855
  dir_defn = @schema_directives.fetch(dir_node.name)
850
- args = arguments(graphql_object, dir_defn, dir_node)
851
- if !dir_defn.include?(graphql_object, args, context)
856
+ raw_dir_args = arguments(nil, dir_defn, dir_node)
857
+ if !raw_dir_args.is_a?(GraphQL::ExecutionError)
858
+ begin
859
+ dir_defn.validate!(raw_dir_args, context)
860
+ rescue GraphQL::ExecutionError => err
861
+ raw_dir_args = err
862
+ end
863
+ end
864
+
865
+ if extra_path_part && raw_dir_args.is_a?(GraphQL::ExecutionError)
866
+ raw_dir_args.path = current_path + [extra_path_part]
867
+ end
868
+
869
+ dir_args = continue_value(
870
+ raw_dir_args, # value
871
+ nil, # field
872
+ false, # is_non_null
873
+ dir_node, # ast_node
874
+ nil, # result_name
875
+ selection_result
876
+ )
877
+ if dir_args == HALT || !dir_defn.include?(graphql_object, dir_args, context)
852
878
  return false
853
879
  end
854
880
  end
@@ -879,7 +905,6 @@ module GraphQL
879
905
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
880
906
  def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, runtime_state:, trace: true, &block)
881
907
  if lazy?(lazy_obj)
882
- orig_result = result
883
908
  was_authorized_by_scope_items = runtime_state.was_authorized_by_scope_items
884
909
  lazy = GraphQL::Execution::Lazy.new(field: field) do
885
910
  # This block might be called in a new fiber;
@@ -889,13 +914,13 @@ module GraphQL
889
914
  runtime_state.current_field = field
890
915
  runtime_state.current_arguments = arguments
891
916
  runtime_state.current_result_name = result_name
892
- runtime_state.current_result = orig_result
917
+ runtime_state.current_result = result
893
918
  runtime_state.was_authorized_by_scope_items = was_authorized_by_scope_items
894
919
  # Wrap the execution of _this_ method with tracing,
895
920
  # but don't wrap the continuation below
896
- result = nil
921
+ sync_result = nil
897
922
  inner_obj = begin
898
- result = if trace
923
+ sync_result = if trace
899
924
  @current_trace.begin_execute_field(field, owner_object, arguments, query)
900
925
  @current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
901
926
  schema.sync_lazy(lazy_obj)
@@ -913,7 +938,7 @@ module GraphQL
913
938
  end
914
939
  ensure
915
940
  if trace
916
- @current_trace.end_execute_field(field, owner_object, arguments, query, result)
941
+ @current_trace.end_execute_field(field, owner_object, arguments, query, sync_result)
917
942
  end
918
943
  end
919
944
  yield(inner_obj, runtime_state)
@@ -923,12 +948,7 @@ module GraphQL
923
948
  lazy.value
924
949
  else
925
950
  set_result(result, result_name, lazy, false, false) # is_non_null is irrelevant here
926
- current_depth = 0
927
- while result
928
- current_depth += 1
929
- result = result.graphql_parent
930
- end
931
- @lazies_at_depth[current_depth] << lazy
951
+ @dataloader.lazy_at_depth(result.depth, lazy)
932
952
  lazy
933
953
  end
934
954
  else
@@ -22,6 +22,7 @@ module GraphQL
22
22
  # @param max_complexity [Integer, nil]
23
23
  # @return [Array<GraphQL::Query::Result>] One result per query
24
24
  def run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
25
+ previous_multiplex = Fiber[:__graphql_current_multiplex]
25
26
  queries = query_options.map do |opts|
26
27
  query = case opts
27
28
  when Hash
@@ -42,7 +43,6 @@ module GraphQL
42
43
  trace.execute_multiplex(multiplex: multiplex) do
43
44
  schema = multiplex.schema
44
45
  queries = multiplex.queries
45
- lazies_at_depth = Hash.new { |h, k| h[k] = [] }
46
46
  multiplex_analyzers = schema.multiplex_analyzers
47
47
  if multiplex.max_complexity
48
48
  multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity]
@@ -59,11 +59,6 @@ module GraphQL
59
59
  # Do as much eager evaluation of the query as possible
60
60
  results = []
61
61
  queries.each_with_index do |query, idx|
62
- if query.subscription? && !query.subscription_update?
63
- subs_namespace = query.context.namespace(:subscriptions)
64
- subs_namespace[:events] = []
65
- subs_namespace[:subscriptions] = {}
66
- end
67
62
  multiplex.dataloader.append_job {
68
63
  operation = query.selected_operation
69
64
  result = if operation.nil? || !query.valid? || !query.context.errors.empty?
@@ -73,38 +68,27 @@ module GraphQL
73
68
  # Although queries in a multiplex _share_ an Interpreter instance,
74
69
  # they also have another item of state, which is private to that query
75
70
  # in particular, assign it here:
76
- runtime = Runtime.new(query: query, lazies_at_depth: lazies_at_depth)
71
+ runtime = Runtime.new(query: query)
77
72
  query.context.namespace(:interpreter_runtime)[:runtime] = runtime
78
-
73
+ if query.subscription? && !query.subscription_update?
74
+ schema.subscriptions.initialize_subscriptions(query)
75
+ end
79
76
  query.current_trace.execute_query(query: query) do
80
77
  runtime.run_eager
81
78
  end
82
79
  rescue GraphQL::ExecutionError => err
83
80
  query.context.errors << err
84
- NO_OPERATION
85
81
  end
86
82
  end
87
83
  results[idx] = result
88
84
  }
89
85
  end
90
86
 
91
- multiplex.dataloader.run
92
-
93
- # Then, work through lazy results in a breadth-first way
94
- multiplex.dataloader.append_job {
95
- query = multiplex.queries.length == 1 ? multiplex.queries[0] : nil
96
- multiplex.current_trace.execute_query_lazy(multiplex: multiplex, query: query) do
97
- Interpreter::Resolve.resolve_each_depth(lazies_at_depth, multiplex.dataloader)
98
- end
99
- }
100
- multiplex.dataloader.run
87
+ multiplex.dataloader.run(trace_query_lazy: multiplex)
101
88
 
102
89
  # Then, find all errors and assign the result to the query object
103
90
  results.each_with_index do |data_result, idx|
104
91
  query = queries[idx]
105
- if (events = query.context.namespace(:subscriptions)[:events]) && !events.empty?
106
- schema.subscriptions.write_subscription(query, events)
107
- end
108
92
  # Assign the result so that it can be accessed in instrumentation
109
93
  query.result_values = if data_result.equal?(NO_OPERATION)
110
94
  if !query.valid? || !query.context.errors.empty?
@@ -114,6 +98,9 @@ module GraphQL
114
98
  data_result
115
99
  end
116
100
  else
101
+ if query.subscription?
102
+ schema.subscriptions.finish_subscriptions(query)
103
+ end
117
104
  result = {}
118
105
 
119
106
  if !query.context.errors.empty?
@@ -133,13 +120,18 @@ module GraphQL
133
120
  end
134
121
 
135
122
  results
123
+ rescue SystemStackError => err
124
+ queries.map do |query|
125
+ schema.query_stack_error(query, err)
126
+ query.result_values ||= { "errors" => query.context.errors.map(&:to_h) }
127
+ query.result
128
+ end
136
129
  rescue Exception
137
130
  # TODO rescue at a higher level so it will catch errors in analysis, too
138
131
  # Assign values here so that the query's `@executed` becomes true
139
132
  queries.map { |q| q.result_values ||= {} }
140
133
  raise
141
134
  ensure
142
- Fiber[:__graphql_current_multiplex] = nil
143
135
  queries.map { |query|
144
136
  runtime = query.context.namespace(:interpreter_runtime)[:runtime]
145
137
  if runtime
@@ -148,6 +140,8 @@ module GraphQL
148
140
  }
149
141
  end
150
142
  end
143
+ ensure
144
+ Fiber[:__graphql_current_multiplex] = previous_multiplex
151
145
  end
152
146
  end
153
147
 
@@ -38,7 +38,7 @@ module GraphQL
38
38
  # (fewer clauses in a hot `case` block), but now it requires special handling here.
39
39
  # I think it's still worth it for the performance win, but if the number of special
40
40
  # cases grows, then maybe it's worth rethinking somehow.
41
- if @value.is_a?(StandardError) && @value != GraphQL::Execution::SKIP
41
+ if @value.is_a?(StandardError) && !@value.is_a?(GraphQL::Execution::Skip)
42
42
  raise @value
43
43
  else
44
44
  @value
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ class LoadArgumentStep
5
+ def initialize(field_resolve_step:, arguments:, load_receiver:, argument_value:, argument_definition:, argument_key:)
6
+ @field_resolve_step = field_resolve_step
7
+ @load_receiver = load_receiver
8
+ @arguments = arguments
9
+ @argument_value = argument_value
10
+ @argument_definition = argument_definition
11
+ @argument_key = argument_key
12
+ @loaded_value = nil
13
+ @is_authorized = true
14
+ end
15
+
16
+ def value
17
+ @field_resolve_step.set_current_field
18
+ schema = @field_resolve_step.runner.schema
19
+ @loaded_value = schema.sync_lazy(@loaded_value)
20
+ assign_value
21
+ rescue GraphQL::UnauthorizedError => auth_err
22
+ @is_authorized = false
23
+ schema.unauthorized_object(auth_err)
24
+ rescue GraphQL::RuntimeError => err
25
+ @loaded_value = if err.is_a?(Schema::Subscription::EarlyUnsubscribe)
26
+ err.unsubscribed_result
27
+ else
28
+ err
29
+ end
30
+ assign_value
31
+ rescue StandardError => stderr
32
+ begin
33
+ @field_resolve_step.selections_step.query.handle_or_reraise(stderr, field: @field_definition, arguments: @arguments, object: nil)
34
+ rescue GraphQL::ExecutionError => ex_err
35
+ @loaded_value = ex_err
36
+ end
37
+ assign_value
38
+ ensure
39
+ @field_resolve_step.set_current_field(nil)
40
+ end
41
+
42
+ def call
43
+ @field_resolve_step.set_current_field
44
+ context = @field_resolve_step.selections_step.query.context
45
+ @loaded_value = begin
46
+ @load_receiver.load_and_authorize_application_object(@argument_definition, @argument_value, context)
47
+ rescue GraphQL::UnauthorizedError => auth_err
48
+ @is_authorized = false
49
+ context.schema.unauthorized_object(auth_err)
50
+ end
51
+ if (runner = @field_resolve_step.runner).resolves_lazies && runner.lazy?(@loaded_value)
52
+ runner.dataloader.lazy_at_depth(@field_resolve_step.path.size, self)
53
+ else
54
+ assign_value
55
+ end
56
+ rescue GraphQL::RuntimeError => err
57
+ @loaded_value = if err.is_a?(Schema::Subscription::EarlyUnsubscribe)
58
+ @is_authorized = false
59
+ err.unsubscribed_result
60
+ else
61
+ err
62
+ end
63
+ assign_value
64
+ rescue StandardError => stderr
65
+ @loaded_value = begin
66
+ context.query.handle_or_reraise(stderr, field: @field_resolve_step.field_definition, arguments: @field_resolve_step.arguments, object: nil) # rubocop:disable Development/ContextIsPassedCop
67
+ rescue GraphQL::ExecutionError => ex_err
68
+ ex_err
69
+ end
70
+ assign_value
71
+ ensure
72
+ @field_resolve_step.set_current_field(nil)
73
+ end
74
+
75
+ private
76
+
77
+ def assign_value
78
+ if @loaded_value.is_a?(GraphQL::RuntimeError)
79
+ @loaded_value.path = @field_resolve_step.path
80
+ @field_resolve_step.arguments = @loaded_value
81
+ elsif @is_authorized == false
82
+ # An unauthorized_object hook ate the error
83
+ @field_resolve_step.arguments = EmptyObjects::EMPTY_HASH
84
+ field_pending_steps = @field_resolve_step.pending_steps
85
+ field_pending_steps.clear
86
+ @field_resolve_step.build_errors_result(nil, nil)
87
+ return
88
+ else
89
+ query = @field_resolve_step.selections_step.query
90
+ query.current_trace.object_loaded(@argument_definition, @loaded_value, query.context)
91
+ @arguments[@argument_key] = @loaded_value
92
+ end
93
+
94
+ field_pending_steps = @field_resolve_step.pending_steps
95
+ field_pending_steps.delete(self)
96
+ if @field_resolve_step.arguments && field_pending_steps.size == 0 # rubocop:disable Development/ContextIsPassedCop
97
+ @field_resolve_step.runner.add_step(@field_resolve_step)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -33,7 +33,7 @@ module GraphQL
33
33
  @queries.each { |q| q.multiplex = self }
34
34
  @context = context
35
35
  @dataloader = @context[:dataloader] ||= @schema.dataloader_class.new
36
- @tracers = schema.tracers + (context[:tracers] || [])
36
+ @tracers = schema.tracers + (context[:tracers] || EmptyObjects::EMPTY_ARRAY)
37
37
  @max_complexity = max_complexity
38
38
  @current_trace = context[:trace] ||= schema.new_trace(multiplex: self)
39
39
  @logger = nil
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+ require "graphql/execution/prepare_object_step"
3
+ require "graphql/execution/input_values"
4
+ require "graphql/execution/field_resolve_step"
5
+ require "graphql/execution/finalize"
6
+ require "graphql/execution/load_argument_step"
7
+ require "graphql/execution/resolve_type_step"
8
+ require "graphql/execution/runner"
9
+ require "graphql/execution/selections_step"
10
+ module GraphQL
11
+ module Execution
12
+ module Finalizer
13
+ attr_accessor :path
14
+
15
+ def finalize_graphql_result(query, result_data, result_key)
16
+ raise RequiredImplementationMissingError, "#{self.class} must implement #finalize_graphql_result(query, result_data, result_key)\n\nresult_data: #{result_data}\nresult_key: #{result_key.inspect}"
17
+ end
18
+
19
+ def ast_node
20
+ ast_nodes&.first
21
+ end
22
+
23
+ def ast_node=(new_node)
24
+ @ast_nodes = [new_node]
25
+ end
26
+
27
+ attr_accessor :ast_nodes
28
+ end
29
+
30
+ module HaltExecution
31
+ end
32
+
33
+ module PostProcessor
34
+ def after_resolve(field_results)
35
+ raise RequiredImplementationMissingError, "#{self.class}#after_resolve should handle `field_results` and return a new value to use"
36
+ end
37
+ end
38
+
39
+ module Next
40
+ module SchemaExtension
41
+ def execute_next(query_str = nil, query: nil, subscription_topic: nil, context: nil, document: nil, operation_name: nil, variables: nil, warden: nil, root_value: nil, validate: true, visibility_profile: nil)
42
+ multiplex_context = if context
43
+ {
44
+ backtrace: context[:backtrace],
45
+ tracers: context[:tracers],
46
+ trace: context[:trace],
47
+ dataloader: context[:dataloader],
48
+ trace_mode: context[:trace_mode],
49
+ }
50
+ else
51
+ {}
52
+ end
53
+ query_opts = {
54
+ query: query || query_str,
55
+ subscription_topic: subscription_topic,
56
+ document: document,
57
+ context: context,
58
+ validate: validate,
59
+ variables: variables,
60
+ root_value: root_value,
61
+ operation_name: operation_name,
62
+ visibility_profile: visibility_profile,
63
+ warden: warden,
64
+ }
65
+ m_results = multiplex_next([query_opts], context: multiplex_context, max_complexity: nil)
66
+ m_results[0]
67
+ end
68
+
69
+ def multiplex_next(query_options, context: {}, max_complexity: self.max_complexity)
70
+ Next.run_all(self, query_options, context: context, max_complexity: max_complexity)
71
+ end
72
+ end
73
+
74
+ def self.use(schema, as_default: false)
75
+ schema.extend(SchemaExtension)
76
+ schema.default_execution_next(as_default)
77
+ end
78
+
79
+ def self.run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
80
+ queries = query_options.map do |opts|
81
+ query = case opts
82
+ when Hash
83
+ schema.query_class.new(schema, nil, **opts)
84
+ when GraphQL::Query, GraphQL::Query::Partial
85
+ opts
86
+ else
87
+ raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})"
88
+ end
89
+ query.context[:__graphql_execute_next] = true
90
+ query
91
+ end
92
+ multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
93
+ runner = Runner.new(multiplex)
94
+ runner.execute
95
+ end
96
+ end
97
+ end
98
+ end