graphql 2.5.9 → 2.5.26

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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  4. data/lib/graphql/analysis.rb +20 -13
  5. data/lib/graphql/dashboard/application_controller.rb +41 -0
  6. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  7. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  8. data/lib/graphql/dashboard/subscriptions.rb +2 -1
  9. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
  10. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
  11. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
  12. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
  13. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
  14. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
  15. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
  16. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
  17. data/lib/graphql/dashboard.rb +11 -73
  18. data/lib/graphql/dataloader/active_record_association_source.rb +14 -2
  19. data/lib/graphql/dataloader/async_dataloader.rb +22 -11
  20. data/lib/graphql/dataloader/null_dataloader.rb +54 -9
  21. data/lib/graphql/dataloader.rb +75 -23
  22. data/lib/graphql/date_encoding_error.rb +1 -1
  23. data/lib/graphql/execution/field_resolve_step.rb +631 -0
  24. data/lib/graphql/execution/finalize.rb +217 -0
  25. data/lib/graphql/execution/input_values.rb +261 -0
  26. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  27. data/lib/graphql/execution/interpreter/resolve.rb +10 -16
  28. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
  29. data/lib/graphql/execution/interpreter/runtime.rb +28 -33
  30. data/lib/graphql/execution/interpreter.rb +8 -22
  31. data/lib/graphql/execution/lazy.rb +1 -1
  32. data/lib/graphql/execution/load_argument_step.rb +64 -0
  33. data/lib/graphql/execution/multiplex.rb +1 -1
  34. data/lib/graphql/execution/next.rb +90 -0
  35. data/lib/graphql/execution/prepare_object_step.rb +128 -0
  36. data/lib/graphql/execution/runner.rb +410 -0
  37. data/lib/graphql/execution/selections_step.rb +91 -0
  38. data/lib/graphql/execution.rb +8 -4
  39. data/lib/graphql/execution_error.rb +17 -10
  40. data/lib/graphql/introspection/directive_type.rb +7 -3
  41. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  42. data/lib/graphql/introspection/entry_points.rb +11 -3
  43. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  44. data/lib/graphql/introspection/field_type.rb +13 -5
  45. data/lib/graphql/introspection/input_value_type.rb +21 -13
  46. data/lib/graphql/introspection/type_type.rb +64 -28
  47. data/lib/graphql/invalid_null_error.rb +11 -5
  48. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  49. data/lib/graphql/language/lexer.rb +20 -9
  50. data/lib/graphql/language/nodes.rb +5 -1
  51. data/lib/graphql/language/parser.rb +1 -0
  52. data/lib/graphql/language.rb +21 -12
  53. data/lib/graphql/pagination/connection.rb +2 -0
  54. data/lib/graphql/pagination/connections.rb +32 -0
  55. data/lib/graphql/query/context.rb +11 -4
  56. data/lib/graphql/query/null_context.rb +9 -3
  57. data/lib/graphql/query/partial.rb +18 -3
  58. data/lib/graphql/query.rb +10 -1
  59. data/lib/graphql/runtime_error.rb +6 -0
  60. data/lib/graphql/schema/addition.rb +3 -1
  61. data/lib/graphql/schema/argument.rb +17 -0
  62. data/lib/graphql/schema/build_from_definition.rb +15 -2
  63. data/lib/graphql/schema/directive.rb +45 -13
  64. data/lib/graphql/schema/field/connection_extension.rb +4 -37
  65. data/lib/graphql/schema/field/scope_extension.rb +18 -13
  66. data/lib/graphql/schema/field.rb +87 -48
  67. data/lib/graphql/schema/field_extension.rb +11 -8
  68. data/lib/graphql/schema/interface.rb +26 -0
  69. data/lib/graphql/schema/list.rb +5 -1
  70. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -11
  71. data/lib/graphql/schema/member/has_arguments.rb +43 -14
  72. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  73. data/lib/graphql/schema/member/has_dataloader.rb +37 -0
  74. data/lib/graphql/schema/member/has_fields.rb +86 -5
  75. data/lib/graphql/schema/member/has_interfaces.rb +2 -2
  76. data/lib/graphql/schema/member/type_system_helpers.rb +16 -2
  77. data/lib/graphql/schema/member.rb +5 -0
  78. data/lib/graphql/schema/non_null.rb +1 -1
  79. data/lib/graphql/schema/object.rb +1 -0
  80. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  81. data/lib/graphql/schema/resolver.rb +60 -1
  82. data/lib/graphql/schema/subscription.rb +0 -2
  83. data/lib/graphql/schema/validator/required_validator.rb +45 -5
  84. data/lib/graphql/schema/visibility/migration.rb +2 -2
  85. data/lib/graphql/schema/visibility/profile.rb +140 -56
  86. data/lib/graphql/schema/visibility.rb +31 -18
  87. data/lib/graphql/schema/wrapper.rb +7 -1
  88. data/lib/graphql/schema.rb +108 -32
  89. data/lib/graphql/static_validation/all_rules.rb +1 -1
  90. data/lib/graphql/static_validation/base_visitor.rb +90 -66
  91. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  92. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  93. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
  94. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
  95. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
  96. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -4
  97. data/lib/graphql/static_validation/rules/fields_will_merge.rb +322 -256
  98. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
  99. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  100. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
  101. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
  102. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
  103. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
  104. data/lib/graphql/static_validation/validation_context.rb +1 -1
  105. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  106. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +34 -10
  107. data/lib/graphql/subscriptions/event.rb +1 -0
  108. data/lib/graphql/subscriptions.rb +36 -1
  109. data/lib/graphql/testing/helpers.rb +12 -9
  110. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  111. data/lib/graphql/testing.rb +1 -0
  112. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  113. data/lib/graphql/tracing/detailed_trace.rb +70 -7
  114. data/lib/graphql/tracing/null_trace.rb +1 -1
  115. data/lib/graphql/tracing/perfetto_trace.rb +209 -79
  116. data/lib/graphql/tracing/sentry_trace.rb +3 -1
  117. data/lib/graphql/tracing/trace.rb +6 -0
  118. data/lib/graphql/type_kinds.rb +1 -0
  119. data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
  120. data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
  121. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  122. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  123. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  124. data/lib/graphql/unauthorized_error.rb +9 -1
  125. data/lib/graphql/version.rb +1 -1
  126. data/lib/graphql.rb +7 -3
  127. metadata +21 -3
@@ -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
@@ -298,8 +297,6 @@ module GraphQL
298
297
  end
299
298
 
300
299
  call_method_on_directives(:resolve, selections_result.graphql_application_value, directives) do
301
- finished_jobs = 0
302
- enqueued_jobs = gathered_selections.size
303
300
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
304
301
  # Field resolution may pause the fiber,
305
302
  # so it wouldn't get to the `Resolve` call that happens below.
@@ -310,12 +307,6 @@ module GraphQL
310
307
  evaluate_selection(
311
308
  result_name, field_ast_nodes_or_ast_node, selections_result
312
309
  )
313
- finished_jobs += 1
314
- if finished_jobs == enqueued_jobs
315
- if target_result
316
- selections_result.merge_into(target_result)
317
- end
318
- end
319
310
  @dataloader.clear_cache
320
311
  }
321
312
  else
@@ -323,15 +314,12 @@ module GraphQL
323
314
  evaluate_selection(
324
315
  result_name, field_ast_nodes_or_ast_node, selections_result
325
316
  )
326
- finished_jobs += 1
327
- if finished_jobs == enqueued_jobs
328
- if target_result
329
- selections_result.merge_into(target_result)
330
- end
331
- end
332
317
  }
333
318
  end
334
319
  end
320
+ if target_result
321
+ selections_result.merge_into(target_result)
322
+ end
335
323
  selections_result
336
324
  end
337
325
  end
@@ -376,6 +364,10 @@ module GraphQL
376
364
  else
377
365
  @query.arguments_cache.dataload_for(ast_node, field_defn, owner_object) do |resolved_arguments|
378
366
  runtime_state = get_current_runtime_state # This might be in a different fiber
367
+ runtime_state.current_field = field_defn
368
+ runtime_state.current_arguments = resolved_arguments
369
+ runtime_state.current_result_name = result_name
370
+ runtime_state.current_result = selections_result
379
371
  evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state)
380
372
  end
381
373
  end
@@ -384,6 +376,8 @@ module GraphQL
384
376
  def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state) # rubocop:disable Metrics/ParameterLists
385
377
  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|
386
378
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
379
+ next if selection_result.collect_result(result_name, resolved_arguments)
380
+
387
381
  return_type_non_null = field_defn.type.non_null?
388
382
  continue_value(resolved_arguments, field_defn, return_type_non_null, ast_node, result_name, selection_result)
389
383
  next
@@ -457,7 +451,7 @@ module GraphQL
457
451
  }
458
452
  end
459
453
 
460
- field_result = call_method_on_directives(:resolve, object, directives) do
454
+ call_method_on_directives(:resolve, object, directives) do
461
455
  if !directives.empty?
462
456
  # This might be executed in a different context; reset this info
463
457
  runtime_state = get_current_runtime_state
@@ -483,6 +477,8 @@ module GraphQL
483
477
  end
484
478
  @current_trace.end_execute_field(field_defn, object, kwarg_arguments, query, app_result)
485
479
  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|
480
+ next if selection_result.collect_result(result_name, inner_result)
481
+
486
482
  owner_type = selection_result.graphql_result_type
487
483
  return_type = field_defn.type
488
484
  continue_value = continue_value(inner_result, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
@@ -499,7 +495,7 @@ module GraphQL
499
495
  # all of its child fields before moving on to the next root mutation field.
500
496
  # (Subselections of this mutation will still be resolved level-by-level.)
501
497
  if selection_result.graphql_is_eager
502
- Interpreter::Resolve.resolve_all([field_result], @dataloader)
498
+ @dataloader.run
503
499
  end
504
500
  end
505
501
 
@@ -557,7 +553,7 @@ module GraphQL
557
553
  path
558
554
  end
559
555
 
560
- HALT = Object.new
556
+ HALT = Object.new.freeze
561
557
  def continue_value(value, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
562
558
  case value
563
559
  when nil
@@ -607,7 +603,7 @@ module GraphQL
607
603
  err
608
604
  end
609
605
  continue_value(next_value, field, is_non_null, ast_node, result_name, selection_result)
610
- elsif GraphQL::Execution::SKIP == value
606
+ elsif value.is_a?(GraphQL::Execution::Skip)
611
607
  # It's possible a lazy was already written here
612
608
  case selection_result
613
609
  when GraphQLResultHash
@@ -678,7 +674,11 @@ module GraphQL
678
674
  rescue GraphQL::ExecutionError => ex_err
679
675
  return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
680
676
  rescue StandardError => err
681
- query.handle_or_reraise(err)
677
+ begin
678
+ query.handle_or_reraise(err)
679
+ rescue GraphQL::ExecutionError => ex_err
680
+ return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
681
+ end
682
682
  end
683
683
  set_result(selection_result, result_name, r, false, is_non_null)
684
684
  r
@@ -761,7 +761,7 @@ module GraphQL
761
761
  idx += 1
762
762
  if use_dataloader_job
763
763
  @dataloader.append_job do
764
- 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)
764
+ 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)
765
765
  end
766
766
  else
767
767
  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)
@@ -803,6 +803,7 @@ module GraphQL
803
803
  end
804
804
 
805
805
  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
806
+ runtime_state ||= get_current_runtime_state
806
807
  runtime_state.current_result_name = this_idx
807
808
  runtime_state.current_result = response_list
808
809
  call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
@@ -890,7 +891,6 @@ module GraphQL
890
891
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
891
892
  def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, runtime_state:, trace: true, &block)
892
893
  if lazy?(lazy_obj)
893
- orig_result = result
894
894
  was_authorized_by_scope_items = runtime_state.was_authorized_by_scope_items
895
895
  lazy = GraphQL::Execution::Lazy.new(field: field) do
896
896
  # This block might be called in a new fiber;
@@ -900,13 +900,13 @@ module GraphQL
900
900
  runtime_state.current_field = field
901
901
  runtime_state.current_arguments = arguments
902
902
  runtime_state.current_result_name = result_name
903
- runtime_state.current_result = orig_result
903
+ runtime_state.current_result = result
904
904
  runtime_state.was_authorized_by_scope_items = was_authorized_by_scope_items
905
905
  # Wrap the execution of _this_ method with tracing,
906
906
  # but don't wrap the continuation below
907
- result = nil
907
+ sync_result = nil
908
908
  inner_obj = begin
909
- result = if trace
909
+ sync_result = if trace
910
910
  @current_trace.begin_execute_field(field, owner_object, arguments, query)
911
911
  @current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
912
912
  schema.sync_lazy(lazy_obj)
@@ -924,7 +924,7 @@ module GraphQL
924
924
  end
925
925
  ensure
926
926
  if trace
927
- @current_trace.end_execute_field(field, owner_object, arguments, query, result)
927
+ @current_trace.end_execute_field(field, owner_object, arguments, query, sync_result)
928
928
  end
929
929
  end
930
930
  yield(inner_obj, runtime_state)
@@ -934,12 +934,7 @@ module GraphQL
934
934
  lazy.value
935
935
  else
936
936
  set_result(result, result_name, lazy, false, false) # is_non_null is irrelevant here
937
- current_depth = 0
938
- while result
939
- current_depth += 1
940
- result = result.graphql_parent
941
- end
942
- @lazies_at_depth[current_depth] << lazy
937
+ @dataloader.lazy_at_depth(result.depth, lazy)
943
938
  lazy
944
939
  end
945
940
  else
@@ -42,7 +42,6 @@ module GraphQL
42
42
  trace.execute_multiplex(multiplex: multiplex) do
43
43
  schema = multiplex.schema
44
44
  queries = multiplex.queries
45
- lazies_at_depth = Hash.new { |h, k| h[k] = [] }
46
45
  multiplex_analyzers = schema.multiplex_analyzers
47
46
  if multiplex.max_complexity
48
47
  multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity]
@@ -59,11 +58,6 @@ module GraphQL
59
58
  # Do as much eager evaluation of the query as possible
60
59
  results = []
61
60
  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
61
  multiplex.dataloader.append_job {
68
62
  operation = query.selected_operation
69
63
  result = if operation.nil? || !query.valid? || !query.context.errors.empty?
@@ -73,38 +67,27 @@ module GraphQL
73
67
  # Although queries in a multiplex _share_ an Interpreter instance,
74
68
  # they also have another item of state, which is private to that query
75
69
  # in particular, assign it here:
76
- runtime = Runtime.new(query: query, lazies_at_depth: lazies_at_depth)
70
+ runtime = Runtime.new(query: query)
77
71
  query.context.namespace(:interpreter_runtime)[:runtime] = runtime
78
-
72
+ if query.subscription? && !query.subscription_update?
73
+ schema.subscriptions.initialize_subscriptions(query)
74
+ end
79
75
  query.current_trace.execute_query(query: query) do
80
76
  runtime.run_eager
81
77
  end
82
78
  rescue GraphQL::ExecutionError => err
83
79
  query.context.errors << err
84
- NO_OPERATION
85
80
  end
86
81
  end
87
82
  results[idx] = result
88
83
  }
89
84
  end
90
85
 
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
86
+ multiplex.dataloader.run(trace_query_lazy: multiplex)
101
87
 
102
88
  # Then, find all errors and assign the result to the query object
103
89
  results.each_with_index do |data_result, idx|
104
90
  query = queries[idx]
105
- if (events = query.context.namespace(:subscriptions)[:events]) && !events.empty?
106
- schema.subscriptions.write_subscription(query, events)
107
- end
108
91
  # Assign the result so that it can be accessed in instrumentation
109
92
  query.result_values = if data_result.equal?(NO_OPERATION)
110
93
  if !query.valid? || !query.context.errors.empty?
@@ -114,6 +97,9 @@ module GraphQL
114
97
  data_result
115
98
  end
116
99
  else
100
+ if query.subscription?
101
+ schema.subscriptions.finish_subscriptions(query)
102
+ end
117
103
  result = {}
118
104
 
119
105
  if !query.context.errors.empty?
@@ -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,64 @@
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
+ end
14
+
15
+ def value
16
+ @loaded_value = @field_resolve_step.sync(@loaded_value)
17
+ assign_value
18
+ end
19
+
20
+ def call
21
+ context = @field_resolve_step.selections_step.query.context
22
+ @loaded_value = begin
23
+ @load_receiver.load_and_authorize_application_object(@argument_definition, @argument_value, context)
24
+ rescue GraphQL::UnauthorizedError => auth_err
25
+ context.schema.unauthorized_object(auth_err)
26
+ end
27
+ if (runner = @field_resolve_step.runner).resolves_lazies && runner.lazy?(@loaded_value)
28
+ runner.dataloader.lazy_at_depth(@field_resolve_step.path.size, self)
29
+ else
30
+ assign_value
31
+ end
32
+ rescue GraphQL::RuntimeError => err
33
+ @loaded_value = err
34
+ assign_value
35
+ rescue StandardError => stderr
36
+ @loaded_value = begin
37
+ context.query.handle_or_reraise(stderr)
38
+ rescue GraphQL::ExecutionError => ex_err
39
+ ex_err
40
+ end
41
+ assign_value
42
+ end
43
+
44
+ private
45
+
46
+ def assign_value
47
+ if @loaded_value.is_a?(GraphQL::RuntimeError)
48
+ @loaded_value.path = @field_resolve_step.path
49
+ @field_resolve_step.arguments = @loaded_value
50
+ else
51
+ query = @field_resolve_step.selections_step.query
52
+ query.current_trace.object_loaded(@argument_definition, @loaded_value, query.context)
53
+ @arguments[@argument_key] = @loaded_value
54
+ end
55
+
56
+ field_pending_steps = @field_resolve_step.pending_steps
57
+ field_pending_steps.delete(self)
58
+ if @field_resolve_step.arguments && field_pending_steps.size == 0 # rubocop:disable Development/ContextIsPassedCop
59
+ @field_resolve_step.runner.add_step(@field_resolve_step)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ 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,90 @@
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/runner"
8
+ require "graphql/execution/selections_step"
9
+ module GraphQL
10
+ module Execution
11
+ module Finalizer
12
+ attr_accessor :path
13
+ def finalize_graphql_result(query, result_data, result_key)
14
+ raise RequiredImplementationMissingError
15
+ end
16
+ end
17
+
18
+ module HaltExecution
19
+ end
20
+
21
+ module PostProcessor
22
+ def after_resolve(field_results)
23
+ raise RequiredImplementationMissingError, "#{self.class}#after_resolve should handle `field_results` and return a new value to use"
24
+ end
25
+ end
26
+
27
+ module Next
28
+ module SchemaExtension
29
+ def execute_next(query_str = nil, context: nil, document: nil, operation_name: nil, variables: nil, root_value: nil, validate: true, visibility_profile: nil)
30
+ multiplex_context = if context
31
+ {
32
+ backtrace: context[:backtrace],
33
+ tracers: context[:tracers],
34
+ trace: context[:trace],
35
+ dataloader: context[:dataloader],
36
+ trace_mode: context[:trace_mode],
37
+ }
38
+ else
39
+ {}
40
+ end
41
+ query_opts = {
42
+ query: query_str,
43
+ document: document,
44
+ context: context,
45
+ validate: validate,
46
+ variables: variables,
47
+ root_value: root_value,
48
+ operation_name: operation_name,
49
+ visibility_profile: visibility_profile,
50
+ }
51
+ m_results = multiplex_next([query_opts], context: multiplex_context, max_complexity: nil)
52
+ m_results[0]
53
+ end
54
+
55
+ def multiplex_next(query_options, context: {}, max_complexity: self.max_complexity)
56
+ Next.run_all(self, query_options, context: context, max_complexity: max_complexity)
57
+ end
58
+
59
+ def execution_next_options
60
+ @execution_next_options || find_inherited_value(:execution_next_options, EmptyObjects::EMPTY_HASH)
61
+ end
62
+
63
+ attr_writer :execution_next_options
64
+ end
65
+
66
+ def self.use(schema, authorization: true)
67
+ schema.extend(SchemaExtension)
68
+ schema.execution_next_options = { authorization: authorization }
69
+ end
70
+
71
+ def self.run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
72
+ queries = query_options.map do |opts|
73
+ query = case opts
74
+ when Hash
75
+ schema.query_class.new(schema, nil, **opts)
76
+ when GraphQL::Query, GraphQL::Query::Partial
77
+ opts
78
+ else
79
+ raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})"
80
+ end
81
+ query.context[:__graphql_execute_next] = true
82
+ query
83
+ end
84
+ multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
85
+ runner = Runner.new(multiplex, **schema.execution_next_options)
86
+ runner.execute
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ class PrepareObjectStep
5
+ def initialize(object:, runner:, graphql_result:, key:, is_non_null:, field_resolve_step:, next_objects:, next_results:, is_from_array:)
6
+ @object = object
7
+ @runner = runner
8
+ @field_resolve_step = field_resolve_step
9
+ @is_non_null = is_non_null
10
+ @next_objects = next_objects
11
+ @next_results = next_results
12
+ @graphql_result = graphql_result
13
+ @resolved_type = nil
14
+ @authorized_value = nil
15
+ @authorization_error = nil
16
+ @key = key
17
+ @next_step = :resolve_type
18
+ @is_from_array = is_from_array
19
+ end
20
+
21
+ def value
22
+ if @authorized_value
23
+ query = @field_resolve_step.selections_step.query
24
+ query.current_trace.begin_authorized(@resolved_type, @object, query.context)
25
+ @authorized_value = @field_resolve_step.sync(@authorized_value)
26
+ query.current_trace.end_authorized(@resolved_type, @object, query.context, @authorized_value)
27
+ elsif @resolved_type
28
+ ctx = @field_resolve_step.selections_step.query.context
29
+ st = @field_resolve_step.static_type
30
+ ctx.query.current_trace.begin_resolve_type(st, @object, ctx)
31
+ @resolved_type, _ignored_value = @field_resolve_step.sync(@resolved_type)
32
+ ctx.query.current_trace.end_resolve_type(st, @object, ctx, @resolved_type)
33
+ end
34
+ @runner.add_step(self)
35
+ end
36
+
37
+ def call
38
+ case @next_step
39
+ when :resolve_type
40
+ static_type = @field_resolve_step.static_type
41
+ if static_type.kind.abstract?
42
+ ctx = @field_resolve_step.selections_step.query.context
43
+ ctx.query.current_trace.begin_resolve_type(static_type, @object, ctx)
44
+ @resolved_type, _ignored_value = @runner.schema.resolve_type(static_type, @object, ctx)
45
+ ctx.query.current_trace.end_resolve_type(static_type, @object, ctx, @resolved_type)
46
+ else
47
+ @resolved_type = static_type
48
+ end
49
+ if @runner.resolves_lazies && @runner.lazy?(@resolved_type)
50
+ @next_step = :authorize
51
+ @runner.dataloader.lazy_at_depth(@field_resolve_step.path.size, self)
52
+ else
53
+ authorize
54
+ end
55
+ when :authorize
56
+ authorize
57
+ when :create_result
58
+ create_result
59
+ else
60
+ raise ArgumentError, "This is a bug, unknown step: #{@next_step.inspect}"
61
+ end
62
+ end
63
+
64
+ def authorize
65
+ if @field_resolve_step.was_scoped && !@resolved_type.reauthorize_scoped_objects
66
+ @authorized_value = @object
67
+ create_result
68
+ return
69
+ end
70
+
71
+ query = @field_resolve_step.selections_step.query
72
+ begin
73
+ query.current_trace.begin_authorized(@resolved_type, @object, query.context)
74
+ @authorized_value = @resolved_type.authorized?(@object, query.context)
75
+ query.current_trace.end_authorized(@resolved_type, @object, query.context, @authorized_value)
76
+ rescue GraphQL::UnauthorizedError => auth_err
77
+ @authorization_error = auth_err
78
+ end
79
+
80
+ if @runner.resolves_lazies && @runner.lazy?(@authorized_value)
81
+ @runner.dataloader.lazy_at_depth(@field_resolve_step.path.size, self)
82
+ @next_step = :create_result
83
+ else
84
+ create_result
85
+ end
86
+ rescue GraphQL::RuntimeError => err
87
+ @graphql_result[@key] = @field_resolve_step.add_graphql_error(err)
88
+ end
89
+
90
+ def create_result
91
+ if !@authorized_value
92
+ @authorization_error ||= GraphQL::UnauthorizedError.new(object: @object, type: @resolved_type, context: @field_resolve_step.selections_step.query.context)
93
+ end
94
+
95
+ if @authorization_error
96
+ begin
97
+ new_obj = @runner.schema.unauthorized_object(@authorization_error)
98
+ if new_obj
99
+ @authorized_value = true
100
+ @object = new_obj
101
+ elsif @is_non_null
102
+ @graphql_result[@key] = @field_resolve_step.add_non_null_error(@is_from_array)
103
+ else
104
+ @graphql_result[@key] = @field_resolve_step.add_graphql_error(@authorization_error)
105
+ end
106
+ rescue GraphQL::RuntimeError => err
107
+ if @is_non_null
108
+ @graphql_result[@key] = @field_resolve_step.add_non_null_error(@is_from_array)
109
+ else
110
+ @graphql_result[@key] = @field_resolve_step.add_graphql_error(err)
111
+ end
112
+ end
113
+ end
114
+
115
+ if @authorized_value
116
+ next_result_h = {}
117
+ @next_results << next_result_h
118
+ @next_objects << @object
119
+ @graphql_result[@key] = next_result_h
120
+ @runner.runtime_type_at[next_result_h] = @resolved_type
121
+ @runner.static_type_at[next_result_h] = @field_resolve_step.static_type
122
+ end
123
+
124
+ @field_resolve_step.authorized_finished(self)
125
+ end
126
+ end
127
+ end
128
+ end