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
@@ -0,0 +1,151 @@
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
+ @field_resolve_step.set_current_field
23
+ if @authorized_value
24
+ query = @field_resolve_step.selections_step.query
25
+ query.current_trace.begin_authorized(@resolved_type, @object, query.context)
26
+ @authorized_value = @field_resolve_step.sync(@authorized_value)
27
+ query.current_trace.end_authorized(@resolved_type, @object, query.context, @authorized_value)
28
+ elsif @resolved_type
29
+ ctx = @field_resolve_step.selections_step.query.context
30
+ st = @field_resolve_step.static_type
31
+ ctx.query.current_trace.begin_resolve_type(st, @object, ctx)
32
+ @resolved_type, new_value = @field_resolve_step.sync(@resolved_type)
33
+ ResolveTypeStep.assert_valid_resolved_type(st, @resolved_type, new_value, @field_resolve_step)
34
+ if new_value
35
+ @object = new_value
36
+ end
37
+ ctx.query.current_trace.end_resolve_type(st, @object, ctx, @resolved_type)
38
+ end
39
+ @runner.add_step(self)
40
+ ensure
41
+ @field_resolve_step.set_current_field(nil)
42
+ end
43
+
44
+ def call
45
+ @field_resolve_step.set_current_field
46
+ case @next_step
47
+ when :resolve_type
48
+ static_type = @field_resolve_step.static_type
49
+ if static_type.kind.abstract?
50
+ query = @field_resolve_step.selections_step.query
51
+ @resolved_type, new_value = ResolveTypeStep.resolve_type(static_type, @object, query)
52
+ if new_value
53
+ ResolveTypeStep.assert_valid_resolved_type(static_type, @resolved_type, new_value, @field_resolve_step)
54
+ @object = new_value
55
+ end
56
+ else
57
+ @resolved_type = static_type
58
+ end
59
+ if @runner.resolves_lazies && @runner.lazy?(@resolved_type)
60
+ @next_step = :authorize
61
+ @runner.dataloader.lazy_at_depth(@field_resolve_step.path.size, self)
62
+ else
63
+ authorize
64
+ end
65
+ when :authorize
66
+ authorize
67
+ when :create_result
68
+ create_result
69
+ else
70
+ raise ArgumentError, "This is a bug, unknown step: #{@next_step.inspect}"
71
+ end
72
+ ensure
73
+ @field_resolve_step.set_current_field(nil)
74
+ end
75
+
76
+ def add_field_error(err)
77
+ @field_resolve_step.add_graphql_error(@graphql_result, @key, err)
78
+ end
79
+
80
+ def authorize
81
+ if @field_resolve_step.was_scoped && !@resolved_type.reauthorize_scoped_objects
82
+ @authorized_value = @object
83
+ create_result
84
+ return
85
+ end
86
+
87
+ query = @field_resolve_step.selections_step.query
88
+ begin
89
+ query.current_trace.begin_authorized(@resolved_type, @object, query.context)
90
+ @authorized_value = @resolved_type.authorized?(@object, query.context)
91
+ query.current_trace.end_authorized(@resolved_type, @object, query.context, @authorized_value)
92
+ rescue GraphQL::UnauthorizedError => auth_err
93
+ @authorization_error = auth_err
94
+ end
95
+
96
+ if @runner.resolves_lazies && @runner.lazy?(@authorized_value)
97
+ @runner.dataloader.lazy_at_depth(@field_resolve_step.path.size, self)
98
+ @next_step = :create_result
99
+ else
100
+ create_result
101
+ end
102
+ rescue GraphQL::RuntimeError => err
103
+ @graphql_result[@key] = add_field_error(err)
104
+ rescue StandardError => err
105
+ query ||= @field_resolve_step.selections_step.query
106
+ begin
107
+ query.handle_or_reraise(err, field: @field_resolve_step.field_definition, arguments: @field_resolve_step.arguments, object: @object) # rubocop:disable Development/ContextIsPassedCop
108
+ rescue GraphQL::RuntimeError => err
109
+ @graphql_result[@key] = add_field_error(err)
110
+ end
111
+ end
112
+
113
+ def create_result
114
+ if !@authorized_value
115
+ @authorization_error ||= GraphQL::UnauthorizedError.new(object: @object, type: @resolved_type, context: @field_resolve_step.selections_step.query.context)
116
+ end
117
+
118
+ if @authorization_error
119
+ begin
120
+ new_obj = @runner.schema.unauthorized_object(@authorization_error)
121
+ if new_obj
122
+ @authorized_value = true
123
+ @object = new_obj
124
+ elsif @is_non_null
125
+ @graphql_result[@key] = @field_resolve_step.add_non_null_error(@is_from_array)
126
+ else
127
+ @graphql_result[@key] = add_field_error(@authorization_error)
128
+ end
129
+ rescue GraphQL::RuntimeError => err
130
+ if @is_non_null
131
+ @graphql_result[@key] = @field_resolve_step.add_non_null_error(@is_from_array)
132
+ else
133
+ @graphql_result[@key] = add_field_error(err)
134
+ end
135
+ end
136
+ end
137
+
138
+ if @authorized_value
139
+ next_result_h = {}
140
+ @next_results << next_result_h
141
+ @next_objects << @object
142
+ @graphql_result[@key] = next_result_h
143
+ @runner.runtime_type_at[next_result_h] = @resolved_type
144
+ @runner.static_type_at[next_result_h] = @field_resolve_step.static_type
145
+ end
146
+
147
+ @field_resolve_step.authorized_finished(self)
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ class ResolveTypeStep
5
+ def self.resolve_type(type, object, query)
6
+ query.current_trace.begin_resolve_type(type, object, query.context)
7
+ resolved_type_response = query.resolve_type(type, object)
8
+ resolved_type = if resolved_type_response.is_a?(Array)
9
+ resolved_type_response.first
10
+ else
11
+ resolved_type_response
12
+ end
13
+ query.current_trace.end_resolve_type(type, object, query.context, resolved_type)
14
+ resolved_type_response
15
+ end
16
+
17
+ def self.assert_valid_resolved_type(abstract_type, resolved_type, object, field_resolution_step, query: field_resolution_step.selections_step.query)
18
+ possible_types = query.types.possible_types(abstract_type)
19
+ if !possible_types.include?(resolved_type)
20
+ err_class = abstract_type::UnresolvedTypeError
21
+ type_error = err_class.new(object, field_resolution_step.field_definition, abstract_type, resolved_type, possible_types)
22
+ query.schema.type_error(type_error, query.context)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,457 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ class Runner
5
+ def initialize(multiplex)
6
+ @multiplex = multiplex
7
+ @schema = multiplex.schema
8
+ @runtime_type_at = {}.compare_by_identity
9
+ @static_type_at = {}.compare_by_identity
10
+ @finalizers = nil
11
+ @selected_operation = nil
12
+ @dataloader = multiplex.context[:dataloader] ||= @schema.dataloader_class.new
13
+ @resolves_lazies = @schema.resolves_lazies?
14
+ @input_values = Hash.new do |h, query|
15
+ h[query] = InputValues.new(query, self)
16
+ end.compare_by_identity
17
+
18
+ @runtime_directives = nil
19
+ @schema.directives.each do |name, dir_class|
20
+ if dir_class.runtime? && name != "include" && name != "skip"
21
+ @runtime_directives ||= {}
22
+ @runtime_directives[dir_class.graphql_name] = dir_class
23
+ end
24
+ end
25
+
26
+ if @runtime_directives.nil?
27
+ @uses_runtime_directives = false
28
+ @runtime_directives = EmptyObjects::EMPTY_HASH
29
+ else
30
+ @uses_runtime_directives = true
31
+ end
32
+
33
+ @lazy_cache = resolves_lazies ? {}.compare_by_identity : nil
34
+ @authorizes_cache = Hash.new do |h, query_context|
35
+ h[query_context] = {}.compare_by_identity
36
+ end.compare_by_identity
37
+ end
38
+
39
+ attr_reader :runtime_directives, :uses_runtime_directives, :finalizer_keys
40
+
41
+ def authorizes?(graphql_definition, query_context)
42
+ auth_cache = @authorizes_cache[query_context]
43
+ case (auth_res = auth_cache[graphql_definition])
44
+ when nil
45
+ auth_cache[graphql_definition] = graphql_definition.authorizes?(query_context)
46
+ else
47
+ auth_res
48
+ end
49
+ end
50
+
51
+ def add_step(step)
52
+ @dataloader.append_job(step)
53
+ end
54
+
55
+ attr_reader :schema, :variables, :dataloader, :resolves_lazies, :authorizes, :static_type_at, :runtime_type_at, :finalizers, :input_values
56
+
57
+ # @return [void]
58
+ def add_finalizer(query, result_value, key, finalizer)
59
+ @finalizers ||= {}.compare_by_identity
60
+ f_for_query = @finalizers[query] ||= {}.compare_by_identity
61
+ f_for_result = f_for_query[result_value] ||= {}.compare_by_identity
62
+ if (f = f_for_result[key])
63
+ if f.is_a?(Array)
64
+ f << finalizer
65
+ else
66
+ f_for_result[key] = [f, finalizer]
67
+ end
68
+ else
69
+ f_for_result[key] = finalizer
70
+ end
71
+ nil
72
+ end
73
+
74
+ def execute
75
+ previous_multiplex = Fiber[:__graphql_current_multiplex]
76
+ Fiber[:__graphql_current_multiplex] = @multiplex
77
+ isolated_steps = [[]]
78
+ trace = @multiplex.current_trace
79
+ queries = @multiplex.queries
80
+ multiplex_analyzers = @schema.multiplex_analyzers
81
+ if @multiplex.max_complexity
82
+ multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity]
83
+ end
84
+
85
+ trace.execute_multiplex(multiplex: @multiplex) do
86
+ trace.begin_analyze_multiplex(@multiplex, multiplex_analyzers)
87
+ @schema.analysis_engine.analyze_multiplex(@multiplex, multiplex_analyzers)
88
+ trace.end_analyze_multiplex(@multiplex, multiplex_analyzers)
89
+
90
+ results = []
91
+ queries.each do |query|
92
+ if query.validate && !query.valid?
93
+ results << {
94
+ "errors" => query.static_errors.map(&:to_h)
95
+ }
96
+ next
97
+ end
98
+
99
+ root_type = query.root_type
100
+
101
+ if root_type.non_null?
102
+ root_type = root_type.of_type
103
+ end
104
+
105
+ root_value = query.root_value
106
+ if resolves_lazies
107
+ root_value = schema.sync_lazy(root_value)
108
+ end
109
+
110
+ trace.execute_query(query: query) do
111
+ begin_execute(isolated_steps, results, query, root_type, root_value)
112
+ end
113
+ rescue GraphQL::RuntimeError => err
114
+ err.ast_node = query.selected_operation
115
+ err.path = query.path
116
+ query.context.add_error(err)
117
+ end
118
+
119
+ trace.execute_query_lazy(query: @multiplex.queries.size == 1 ? @multiplex.queries.first : nil, multiplex: @multiplex) do
120
+ while (next_isolated_steps = isolated_steps.shift)
121
+ next_isolated_steps.each do |step|
122
+ add_step(step)
123
+ end
124
+ @dataloader.run
125
+ end
126
+ end
127
+
128
+ queries.each_with_index.map do |query, idx|
129
+ result = results[idx]
130
+
131
+ fin_result = if (!@finalizers&.key?(query) && query.context.errors.empty?) || !query.valid?
132
+ result
133
+ else
134
+ if result
135
+ data = result["data"]
136
+ data = Finalize.new(query, data, self).run
137
+ end
138
+ errors = []
139
+ query.context.errors.each do |err|
140
+ if err.respond_to?(:to_h)
141
+ errors << err.to_h
142
+ end
143
+ end
144
+ res_h = {}
145
+ if !errors.empty?
146
+ res_h["errors"] = errors
147
+ end
148
+ res_h["data"] = data
149
+ res_h
150
+ end
151
+
152
+ query.result_values = fin_result
153
+ if query.context.namespace?(:__query_result_extensions__)
154
+ query.result_values["extensions"] = query.context.namespace(:__query_result_extensions__)
155
+ end
156
+ query.result
157
+ end
158
+ end
159
+ rescue SystemStackError => err
160
+ @multiplex.queries.map do |query|
161
+ @schema.query_stack_error(query, err)
162
+ query.result_values ||= { "errors" => query.context.errors.map(&:to_h) }
163
+ query.result
164
+ end
165
+ ensure
166
+ Fiber[:__graphql_current_multiplex] = previous_multiplex
167
+ end
168
+
169
+ def gather_selections(type_defn, ast_selections, selections_step, query, all_selections, prototype_result, into:)
170
+ ast_selections.each do |ast_selection|
171
+ next if !directives_include?(query, ast_selection)
172
+
173
+ case ast_selection
174
+ when GraphQL::Language::Nodes::Field
175
+ key = ast_selection.alias || ast_selection.name
176
+ step = into[key] ||= begin
177
+ prototype_result[key] = nil
178
+
179
+ FieldResolveStep.new(
180
+ selections_step: selections_step,
181
+ key: key,
182
+ parent_type: type_defn,
183
+ runner: self,
184
+ )
185
+ end
186
+ step.append_selection(ast_selection)
187
+ when GraphQL::Language::Nodes::InlineFragment
188
+ type_condition = ast_selection.type&.name
189
+ if type_condition.nil? || type_condition_applies?(query.context, type_defn, type_condition)
190
+ if uses_runtime_directives && !ast_selection.directives.empty?
191
+ all_selections << (into = { __node: ast_selection })
192
+ all_selections << (prototype_result = {})
193
+ end
194
+ gather_selections(type_defn, ast_selection.selections, selections_step, query, all_selections, prototype_result, into: into)
195
+ end
196
+ when GraphQL::Language::Nodes::FragmentSpread
197
+ fragment_definition = query.fragments[ast_selection.name]
198
+ type_condition = fragment_definition.type.name
199
+ if type_condition_applies?(query.context, type_defn, type_condition)
200
+ if uses_runtime_directives && !ast_selection.directives.empty?
201
+ all_selections << (into = { __node: ast_selection })
202
+ all_selections << (prototype_result = {})
203
+ end
204
+ gather_selections(type_defn, fragment_definition.selections, selections_step, query, all_selections, prototype_result, into: into)
205
+ end
206
+ else
207
+ raise ArgumentError, "Unsupported graphql selection node: #{ast_selection.class} (#{ast_selection.inspect})"
208
+ end
209
+ end
210
+ end
211
+
212
+ def lazy?(object)
213
+ obj_class = object.class
214
+ is_lazy = @lazy_cache[obj_class]
215
+ if is_lazy.nil?
216
+ is_lazy = @lazy_cache[obj_class] = @schema.lazy?(object)
217
+ end
218
+ is_lazy
219
+ end
220
+
221
+ def type_condition_applies?(context, concrete_type, type_name)
222
+ if type_name == concrete_type.graphql_name
223
+ true
224
+ else
225
+ abs_t = @schema.get_type(type_name, context)
226
+ p_types = @schema.possible_types(abs_t, context)
227
+ c_p_types = @schema.possible_types(concrete_type, context)
228
+ p_types.any? { |t| c_p_types.include?(t) }
229
+ end
230
+ end
231
+
232
+ private
233
+
234
+ def begin_execute(isolated_steps, results, query, root_type, root_value)
235
+ data = {}
236
+ @static_type_at[data] = root_type
237
+ selected_operation = query.selected_operation
238
+ beginning_path = query.path
239
+
240
+ case root_type.kind.name
241
+ when "OBJECT"
242
+ if authorizes?(root_type, query.context)
243
+ query.current_trace.begin_authorized(root_type, root_value, query.context)
244
+ auth_check = schema.sync_lazy(root_type.authorized?(root_value, query.context))
245
+ query.current_trace.end_authorized(root_type, root_value, query.context, auth_check)
246
+ root_value = if auth_check
247
+ root_value
248
+ else
249
+ begin
250
+ auth_err = GraphQL::UnauthorizedError.new(object: root_value, type: root_type, context: query.context)
251
+ new_val = schema.unauthorized_object(auth_err)
252
+ if new_val
253
+ auth_check = true
254
+ end
255
+ new_val
256
+ rescue GraphQL::ExecutionError => ex_err
257
+ # The old runtime didn't add path and ast_nodes to this
258
+ ex_err.path = beginning_path
259
+ query.context.add_error(ex_err)
260
+ nil
261
+ end
262
+ end
263
+
264
+ if !auth_check
265
+ results << {}
266
+ return
267
+ end
268
+ end
269
+
270
+ results << { "data" => data }
271
+ objects = [root_value]
272
+ query.current_trace.objects(root_type, objects, query.context)
273
+
274
+ if query.is_a?(GraphQL::Query) && uses_runtime_directives && (query_dirs = selected_operation.directives).any? # rubocop:disable Development/NoneWithoutBlockCop
275
+ continue_execution = true
276
+ query_dirs.each do |dir_node|
277
+ dir_defn = runtime_directives[dir_node.name] || raise(GraphQL::Error, "No directive definition found for: #{dir_node.name.inspect}")
278
+ dir_args, errors = input_values[query].argument_values(dir_defn, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
279
+ if errors
280
+ errors.each { |e|
281
+ e.ast_node = dir_node
282
+ e.path = beginning_path
283
+ query.context.add_error(e)
284
+ }
285
+ continue_execution = false
286
+ break
287
+ end
288
+ result = dir_defn.resolve_operation(selected_operation, query, objects, dir_args, query.context)
289
+ if result.is_a?(Finalizer)
290
+ result.path = beginning_path
291
+ add_finalizer(query, data, nil, result)
292
+ if result.is_a?(HaltExecution)
293
+ continue_execution = false
294
+ break
295
+ end
296
+ end
297
+ end
298
+
299
+ if !continue_execution
300
+ return
301
+ end
302
+ end
303
+
304
+ if query.query?
305
+ isolated_steps[0] << SelectionsStep.new(
306
+ parent_type: root_type,
307
+ field_resolve_step: nil,
308
+ selections: selected_operation.selections,
309
+ objects: objects,
310
+ results: [data],
311
+ path: beginning_path,
312
+ runner: self,
313
+ query: query,
314
+ )
315
+ elsif query.mutation?
316
+ fields = {}
317
+ all_selections = [fields, (prototype_result = {})]
318
+ gather_selections(root_type, selected_operation.selections, nil, query, all_selections, prototype_result, into: fields)
319
+ if all_selections.length > 2
320
+ # TODO DRY with SelectionsStep with directive handling
321
+ raise "Directives on root mutation type not implemented yet"
322
+ end
323
+ fields.each_value do |field_resolve_step|
324
+ isolated_steps << [SelectionsStep.new(
325
+ clobber: false, # `data` is being shared among several selections steps
326
+ parent_type: root_type,
327
+ field_resolve_step: field_resolve_step,
328
+ selections: field_resolve_step.ast_nodes || Array(field_resolve_step.ast_node),
329
+ objects: objects,
330
+ results: [data],
331
+ path: beginning_path,
332
+ runner: self,
333
+ query: query,
334
+ )]
335
+ end
336
+ elsif query.subscription?
337
+ if !query.subscription_update?
338
+ schema.subscriptions.initialize_subscriptions(query)
339
+ add_finalizer(query, data, nil, schema.subscriptions.finalizer)
340
+ end
341
+ isolated_steps[0] << SelectionsStep.new(
342
+ parent_type: root_type,
343
+ field_resolve_step: nil,
344
+ selections: selected_operation.selections,
345
+ objects: objects,
346
+ results: [data],
347
+ path: beginning_path,
348
+ runner: self,
349
+ query: query,
350
+ )
351
+ else
352
+ raise ArgumentError, "Unknown operation type (not query, mutation or subscription): #{query.query_string}"
353
+ end
354
+ when "UNION", "INTERFACE"
355
+ resolved_type = ResolveTypeStep.resolve_type(root_type, root_value, query)
356
+ if resolves_lazies && lazy?(resolved_type)
357
+ resolved_type = schema.sync_lazy(resolved_type)
358
+ end
359
+ resolved_type, root_value = resolved_type
360
+ ResolveTypeStep.assert_valid_resolved_type(root_type, resolved_type, root_value, nil, query: query)
361
+ objects = [root_value]
362
+ query.current_trace.objects(resolved_type, objects, query.context)
363
+ runtime_type_at[data] = resolved_type
364
+ results << { "data" => data }
365
+ isolated_steps[0] << SelectionsStep.new(
366
+ parent_type: resolved_type,
367
+ field_resolve_step: nil,
368
+ selections: selected_operation.selections,
369
+ objects: objects,
370
+ results: [data],
371
+ path: beginning_path,
372
+ runner: self,
373
+ query: query,
374
+ )
375
+ when "LIST"
376
+ inner_type = root_type.unwrap
377
+ case inner_type.kind.name
378
+ when "SCALAR", "ENUM"
379
+ results << run_isolated_scalar(root_type, query)
380
+ else
381
+ list_result = Array.new(root_value.size) { Hash.new.compare_by_identity }
382
+ results << { "data" => list_result }
383
+ isolated_steps[0] << SelectionsStep.new(
384
+ parent_type: inner_type,
385
+ field_resolve_step: nil,
386
+ selections: selected_operation.selections,
387
+ objects: root_value,
388
+ results: list_result,
389
+ path: beginning_path,
390
+ runner: self,
391
+ query: query,
392
+ )
393
+ end
394
+ when "SCALAR", "ENUM"
395
+ results << run_isolated_scalar(root_type, query)
396
+ else
397
+ raise "Unhandled root type kind: #{root_type.kind.name.inspect}"
398
+ end
399
+ end
400
+
401
+ def directives_include?(query, ast_selection)
402
+ if ast_selection.directives.any? { |dir_node|
403
+ case dir_node.name
404
+ when "skip"
405
+ skip_args, _errors = @input_values[query].argument_values(GraphQL::Schema::Directive::Skip, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
406
+ skip_args[:if] == true
407
+ when "include"
408
+ include_args, _errors = @input_values[query].argument_values(GraphQL::Schema::Directive::Include, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
409
+ include_args[:if] == false
410
+ else
411
+ dir_defn = runtime_directives[dir_node.name]
412
+ dir_args, _errors = @input_values[query].argument_values(dir_defn, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
413
+ !dir_defn.include?(nil, dir_args, query.context)
414
+ end
415
+ }
416
+ false
417
+ else
418
+ true
419
+ end
420
+ end
421
+
422
+ def run_isolated_scalar(type, partial)
423
+ value = partial.root_value
424
+ dummy_path = partial.path.dup
425
+ key = dummy_path.pop
426
+ is_from_array = key.is_a?(Integer)
427
+
428
+ if lazy?(value)
429
+ value = @schema.sync_lazy(value)
430
+ end
431
+ selections = partial.ast_nodes
432
+ dummy_ss = SelectionsStep.new(
433
+ parent_type: nil,
434
+ field_resolve_step: nil,
435
+ selections: selections,
436
+ objects: nil,
437
+ results: nil,
438
+ path: dummy_path,
439
+ runner: self,
440
+ query: partial,
441
+ )
442
+ dummy_frs = FieldResolveStep.new(
443
+ selections_step: dummy_ss,
444
+ key: key,
445
+ parent_type: nil,
446
+ runner: self,
447
+ )
448
+ dummy_frs.static_type = type
449
+ selections.each { |s| dummy_frs.append_selection(s) }
450
+
451
+ result = is_from_array ? [] : {}
452
+ dummy_frs.finish_leaf_result(result, key, value, type, partial.context)
453
+ { "data" => result[key] }
454
+ end
455
+ end
456
+ end
457
+ end