graphql 2.5.19 → 2.5.21

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/dashboard/application_controller.rb +41 -0
  3. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  4. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  5. data/lib/graphql/dashboard/subscriptions.rb +2 -1
  6. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +3 -3
  7. data/lib/graphql/dashboard.rb +9 -74
  8. data/lib/graphql/dataloader/null_dataloader.rb +7 -3
  9. data/lib/graphql/execution/multiplex.rb +1 -1
  10. data/lib/graphql/execution/next/field_resolve_step.rb +690 -0
  11. data/lib/graphql/execution/next/load_argument_step.rb +60 -0
  12. data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
  13. data/lib/graphql/execution/next/runner.rb +389 -0
  14. data/lib/graphql/execution/next/selections_step.rb +37 -0
  15. data/lib/graphql/execution/next.rb +69 -0
  16. data/lib/graphql/execution.rb +1 -0
  17. data/lib/graphql/execution_error.rb +13 -10
  18. data/lib/graphql/introspection/directive_type.rb +7 -3
  19. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  20. data/lib/graphql/introspection/entry_points.rb +11 -3
  21. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  22. data/lib/graphql/introspection/field_type.rb +13 -5
  23. data/lib/graphql/introspection/input_value_type.rb +21 -13
  24. data/lib/graphql/introspection/type_type.rb +64 -28
  25. data/lib/graphql/invalid_null_error.rb +11 -5
  26. data/lib/graphql/pagination/connection.rb +2 -0
  27. data/lib/graphql/pagination/connections.rb +32 -0
  28. data/lib/graphql/query/context.rb +3 -2
  29. data/lib/graphql/query/null_context.rb +9 -3
  30. data/lib/graphql/schema/argument.rb +5 -0
  31. data/lib/graphql/schema/build_from_definition.rb +7 -0
  32. data/lib/graphql/schema/field/connection_extension.rb +15 -35
  33. data/lib/graphql/schema/field/scope_extension.rb +22 -13
  34. data/lib/graphql/schema/field.rb +70 -1
  35. data/lib/graphql/schema/field_extension.rb +33 -0
  36. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
  37. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  38. data/lib/graphql/schema/member/has_dataloader.rb +17 -0
  39. data/lib/graphql/schema/member/has_fields.rb +5 -1
  40. data/lib/graphql/schema/member.rb +5 -0
  41. data/lib/graphql/schema/object.rb +1 -0
  42. data/lib/graphql/schema/resolver.rb +45 -1
  43. data/lib/graphql/schema/visibility.rb +1 -1
  44. data/lib/graphql/schema.rb +33 -7
  45. data/lib/graphql/subscriptions.rb +1 -1
  46. data/lib/graphql/tracing/perfetto_trace.rb +1 -1
  47. data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
  48. data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
  49. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  50. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  51. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  52. data/lib/graphql/unauthorized_error.rb +5 -1
  53. data/lib/graphql/version.rb +1 -1
  54. data/lib/graphql.rb +3 -0
  55. metadata +12 -2
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ module Next
5
+ class LoadArgumentStep
6
+ def initialize(field_resolve_step:, arguments:, load_receiver:, argument_value:, argument_definition:, argument_key:)
7
+ @field_resolve_step = field_resolve_step
8
+ @load_receiver = load_receiver
9
+ @arguments = arguments
10
+ @argument_value = argument_value
11
+ @argument_definition = argument_definition
12
+ @argument_key = argument_key
13
+ @loaded_value = nil
14
+ end
15
+
16
+ def value
17
+ @loaded_value = @field_resolve_step.sync(@loaded_value)
18
+ assign_value
19
+ end
20
+
21
+ def call
22
+ context = @field_resolve_step.selections_step.query.context
23
+ @loaded_value = @load_receiver.load_and_authorize_application_object(@argument_definition, @argument_value, context)
24
+ if (runner = @field_resolve_step.runner).resolves_lazies && runner.lazy?(@loaded_value)
25
+ runner.dataloader.lazy_at_depth(@field_resolve_step.path.size, self)
26
+ else
27
+ assign_value
28
+ end
29
+ rescue GraphQL::RuntimeError => err
30
+ @loaded_value = err
31
+ assign_value
32
+ rescue StandardError => stderr
33
+ @loaded_value = begin
34
+ context.query.handle_or_reraise(stderr)
35
+ rescue GraphQL::ExecutionError => ex_err
36
+ ex_err
37
+ end
38
+ assign_value
39
+ end
40
+
41
+ private
42
+
43
+ def assign_value
44
+ if @loaded_value.is_a?(GraphQL::Error)
45
+ @loaded_value.path = @field_resolve_step.path
46
+ @field_resolve_step.arguments = @loaded_value
47
+ else
48
+ @arguments[@argument_key] = @loaded_value
49
+ end
50
+
51
+ field_pending_steps = @field_resolve_step.pending_steps
52
+ field_pending_steps.delete(self)
53
+ if @field_resolve_step.arguments && field_pending_steps.size == 0 # rubocop:disable Development/ContextIsPassedCop
54
+ @field_resolve_step.runner.add_step(@field_resolve_step)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ module Next
5
+ class PrepareObjectStep
6
+ def initialize(static_type:, object:, runner:, graphql_result:, key:, is_non_null:, field_resolve_step:, next_objects:, next_results:, is_from_array:)
7
+ @static_type = static_type
8
+ @object = object
9
+ @runner = runner
10
+ @field_resolve_step = field_resolve_step
11
+ @is_non_null = is_non_null
12
+ @next_objects = next_objects
13
+ @next_results = next_results
14
+ @graphql_result = graphql_result
15
+ @resolved_type = nil
16
+ @authorized_value = nil
17
+ @authorization_error = nil
18
+ @key = key
19
+ @next_step = :resolve_type
20
+ @is_from_array = is_from_array
21
+ end
22
+
23
+ def value
24
+ if @authorized_value
25
+ query = @field_resolve_step.selections_step.query
26
+ query.current_trace.begin_authorized(@resolved_type, @object, query.context)
27
+ @authorized_value = @field_resolve_step.sync(@authorized_value)
28
+ query.current_trace.end_authorized(@resolved_type, @object, query.context, @authorized_value)
29
+ elsif @resolved_type
30
+ ctx = @field_resolve_step.selections_step.query.context
31
+ ctx.query.current_trace.begin_resolve_type(@static_type, @object, ctx)
32
+ @resolved_type, _ignored_value = @field_resolve_step.sync(@resolved_type)
33
+ ctx.query.current_trace.end_resolve_type(@static_type, @object, ctx, @resolved_type)
34
+ end
35
+ @runner.add_step(self)
36
+ end
37
+
38
+ def call
39
+ case @next_step
40
+ when :resolve_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(@resolve_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::Error => 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::Error => 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] = @static_type
122
+ end
123
+
124
+ @field_resolve_step.authorized_finished(self)
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,389 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ module Next
5
+ class Runner
6
+ def initialize(multiplex, authorization:)
7
+ @multiplex = multiplex
8
+ @schema = multiplex.schema
9
+ @steps_queue = []
10
+ @runtime_type_at = {}.compare_by_identity
11
+ @static_type_at = {}.compare_by_identity
12
+ @selected_operation = nil
13
+ @dataloader = multiplex.context[:dataloader] ||= @schema.dataloader_class.new
14
+ @resolves_lazies = @schema.resolves_lazies?
15
+ @lazy_cache = resolves_lazies ? {}.compare_by_identity : nil
16
+ @field_resolve_step_class = @schema.uses_raw_value? ? RawValueFieldResolveStep : FieldResolveStep
17
+ @authorization = authorization
18
+ if @authorization
19
+ @authorizes_cache = Hash.new do |h, query_context|
20
+ h[query_context] = {}.compare_by_identity
21
+ end.compare_by_identity
22
+ end
23
+ end
24
+
25
+ def resolve_type(type, object, query)
26
+ query.current_trace.begin_resolve_type(@static_type, object, query.context)
27
+ resolved_type, _ignored_new_value = query.resolve_type(type, object)
28
+ query.current_trace.end_resolve_type(@static_type, object, query.context, resolved_type)
29
+ resolved_type
30
+ end
31
+
32
+ def authorizes?(graphql_definition, query_context)
33
+ auth_cache = @authorizes_cache[query_context]
34
+ case (auth_res = auth_cache[graphql_definition])
35
+ when nil
36
+ auth_cache[graphql_definition] = graphql_definition.authorizes?(query_context)
37
+ else
38
+ auth_res
39
+ end
40
+ end
41
+
42
+ def add_step(step)
43
+ @dataloader.append_job(step)
44
+ end
45
+
46
+ attr_reader :authorization, :steps_queue, :schema, :variables, :dataloader, :resolves_lazies, :authorizes, :static_type_at, :runtime_type_at
47
+
48
+ def execute
49
+ Fiber[:__graphql_current_multiplex] = @multiplex
50
+ isolated_steps = [[]]
51
+ trace = @multiplex.current_trace
52
+ queries = @multiplex.queries
53
+ multiplex_analyzers = @schema.multiplex_analyzers
54
+ if @multiplex.max_complexity
55
+ multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity]
56
+ end
57
+
58
+ trace.execute_multiplex(multiplex: @multiplex) do
59
+ trace.begin_analyze_multiplex(@multiplex, multiplex_analyzers)
60
+ @schema.analysis_engine.analyze_multiplex(@multiplex, multiplex_analyzers)
61
+ trace.end_analyze_multiplex(@multiplex, multiplex_analyzers)
62
+
63
+ results = []
64
+ queries.each do |query|
65
+ if query.validate && !query.valid?
66
+ results << {
67
+ "errors" => query.static_errors.map(&:to_h)
68
+ }
69
+ next
70
+ end
71
+
72
+ selected_operation = query.document.definitions.first # TODO select named operation
73
+ data = {}
74
+
75
+ root_type = case selected_operation.operation_type
76
+ when nil, "query"
77
+ @schema.query
78
+ when "mutation"
79
+ @schema.mutation
80
+ when "subscription"
81
+ @schema.subscription
82
+ else
83
+ raise ArgumentError, "Unknown operation type: #{selected_operation.operation_type.inspect}"
84
+ end
85
+
86
+ if self.authorization && authorizes?(root_type, query.context)
87
+ query.current_trace.begin_authorized(root_type, query.root_value, query.context)
88
+ auth_check = schema.sync_lazy(root_type.authorized?(query.root_value, query.context))
89
+ query.current_trace.end_authorized(root_type, query.root_value, query.context, auth_check)
90
+ root_value = if auth_check
91
+ query.root_value
92
+ else
93
+ begin
94
+ auth_err = GraphQL::UnauthorizedError.new(object: query.root_value, type: root_type, context: query.context)
95
+ new_val = schema.unauthorized_object(auth_err)
96
+ if new_val
97
+ auth_check = true
98
+ end
99
+ new_val
100
+ rescue GraphQL::ExecutionError => ex_err
101
+ # The old runtime didn't add path and ast_nodes to this
102
+ query.context.add_error(ex_err)
103
+ nil
104
+ end
105
+ end
106
+
107
+ if !auth_check
108
+ results << {}
109
+ next
110
+ end
111
+ else
112
+ root_value = query.root_value
113
+ end
114
+
115
+ results << { "data" => data }
116
+
117
+ case selected_operation.operation_type
118
+ when nil, "query"
119
+ isolated_steps[0] << SelectionsStep.new(
120
+ parent_type: root_type,
121
+ selections: selected_operation.selections,
122
+ objects: [root_value],
123
+ results: [data],
124
+ path: EmptyObjects::EMPTY_ARRAY,
125
+ runner: self,
126
+ query: query,
127
+ )
128
+ when "mutation"
129
+ fields = {}
130
+ gather_selections(root_type, selected_operation.selections, nil, query, {}, into: fields)
131
+ fields.each_value do |field_resolve_step|
132
+ isolated_steps << [SelectionsStep.new(
133
+ parent_type: root_type,
134
+ selections: field_resolve_step.ast_nodes || Array(field_resolve_step.ast_node),
135
+ objects: [root_value],
136
+ results: [data],
137
+ path: EmptyObjects::EMPTY_ARRAY,
138
+ runner: self,
139
+ query: query,
140
+ )]
141
+ end
142
+ when "subscription"
143
+ raise ArgumentError, "TODO implement subscriptions"
144
+ else
145
+ raise ArgumentError, "Unhandled operation type: #{operation.operation_type.inspect}"
146
+ end
147
+
148
+ @static_type_at[data] = root_type
149
+
150
+ # TODO This is stupid but makes multiplex_spec.rb pass
151
+ trace.execute_query(query: query) do
152
+ end
153
+ end
154
+
155
+ while (next_isolated_steps = isolated_steps.shift)
156
+ next_isolated_steps.each do |step|
157
+ add_step(step)
158
+ end
159
+ @dataloader.run
160
+ end
161
+
162
+ # TODO This is stupid but makes multiplex_spec.rb pass
163
+ trace.execute_query_lazy(query: nil, multiplex: @multiplex) do
164
+ end
165
+
166
+ queries.each_with_index.map do |query, idx|
167
+ result = results[idx]
168
+ fin_result = if query.context.errors.empty?
169
+ result
170
+ else
171
+ data = result["data"]
172
+ data = propagate_errors(data, query)
173
+ errors = []
174
+ query.context.errors.each do |err|
175
+ if err.respond_to?(:to_h)
176
+ errors << err.to_h
177
+ end
178
+ end
179
+ res_h = {}
180
+ if !errors.empty?
181
+ res_h["errors"] = errors
182
+ end
183
+ res_h["data"] = data
184
+ res_h
185
+ end
186
+
187
+ GraphQL::Query::Result.new(query: query, values: fin_result)
188
+ end
189
+ end
190
+ ensure
191
+ Fiber[:__graphql_current_multiplex] = nil
192
+ end
193
+
194
+ def gather_selections(type_defn, ast_selections, selections_step, query, prototype_result, into:)
195
+ ast_selections.each do |ast_selection|
196
+ next if !directives_include?(query, ast_selection)
197
+ case ast_selection
198
+ when GraphQL::Language::Nodes::Field
199
+ key = ast_selection.alias || ast_selection.name
200
+ step = into[key] ||= begin
201
+ prototype_result[key] = nil
202
+
203
+ @field_resolve_step_class.new(
204
+ selections_step: selections_step,
205
+ key: key,
206
+ parent_type: type_defn,
207
+ runner: self,
208
+ )
209
+ end
210
+ step.append_selection(ast_selection)
211
+ when GraphQL::Language::Nodes::InlineFragment
212
+ type_condition = ast_selection.type&.name
213
+ if type_condition.nil? || type_condition_applies?(query.context, type_defn, type_condition)
214
+ gather_selections(type_defn, ast_selection.selections, selections_step, query, prototype_result, into: into)
215
+ end
216
+ when GraphQL::Language::Nodes::FragmentSpread
217
+ fragment_definition = query.document.definitions.find { |defn| defn.is_a?(GraphQL::Language::Nodes::FragmentDefinition) && defn.name == ast_selection.name }
218
+ type_condition = fragment_definition.type.name
219
+ if type_condition_applies?(query.context, type_defn, type_condition)
220
+ gather_selections(type_defn, fragment_definition.selections, selections_step, query, prototype_result, into: into)
221
+ end
222
+ else
223
+ raise ArgumentError, "Unsupported graphql selection node: #{ast_selection.class} (#{ast_selection.inspect})"
224
+ end
225
+ end
226
+ end
227
+
228
+ def lazy?(object)
229
+ obj_class = object.class
230
+ is_lazy = @lazy_cache[obj_class]
231
+ if is_lazy.nil?
232
+ is_lazy = @lazy_cache[obj_class] = @schema.lazy?(object)
233
+ end
234
+ is_lazy
235
+ end
236
+
237
+ private
238
+
239
+ def propagate_errors(data, query)
240
+ paths_to_check = query.context.errors.map(&:path)
241
+ paths_to_check.compact! # root-level auth errors currently come without a path
242
+ # TODO dry with above?
243
+ # This is also where a query-level "Step" would be used?
244
+ selected_operation = query.document.definitions.first # TODO pick a selected operation
245
+ root_type = case selected_operation.operation_type
246
+ when nil, "query"
247
+ query.schema.query
248
+ when "mutation"
249
+ query.schema.mutation
250
+ when "subscription"
251
+ raise "Not implemented yet, TODO"
252
+ end
253
+ check_object_result(query, data, root_type, selected_operation.selections, [], [], paths_to_check)
254
+ end
255
+
256
+ def check_object_result(query, result_h, static_type, ast_selections, current_exec_path, current_result_path, paths_to_check)
257
+ current_path_len = current_exec_path.length
258
+ ast_selections.each do |ast_selection|
259
+ case ast_selection
260
+ when Language::Nodes::Field
261
+ begin
262
+ key = ast_selection.alias || ast_selection.name
263
+ current_exec_path << key
264
+ current_result_path << key
265
+ if paths_to_check.any? { |path_to_check| path_to_check[current_path_len] == key }
266
+ result_value = result_h[key]
267
+ field_defn = query.context.types.field(static_type, ast_selection.name)
268
+ result_type = field_defn.type
269
+ if (result_type_non_null = result_type.non_null?)
270
+ result_type = result_type.of_type
271
+ end
272
+ new_result_value = if result_value.is_a?(GraphQL::Error)
273
+ result_value.path = current_result_path.dup
274
+ nil
275
+ else
276
+ if result_type.list?
277
+ check_list_result(query, result_value, result_type.of_type, ast_selection.selections, current_exec_path, current_result_path, paths_to_check)
278
+ elsif result_type.kind.leaf?
279
+ result_value
280
+ else
281
+ check_object_result(query, result_value, result_type, ast_selection.selections, current_exec_path, current_result_path, paths_to_check)
282
+ end
283
+ end
284
+
285
+ if new_result_value.nil? && result_type_non_null
286
+ return nil
287
+ else
288
+ result_h[key] = new_result_value
289
+ end
290
+ end
291
+ ensure
292
+ current_exec_path.pop
293
+ current_result_path.pop
294
+ end
295
+ when Language::Nodes::InlineFragment
296
+ static_type_at_result = @static_type_at[result_h]
297
+ if static_type_at_result && type_condition_applies?(query.context, static_type_at_result, ast_selection.type.name)
298
+ result_h = check_object_result(query, result_h, static_type, ast_selection.selections, current_exec_path, current_result_path, paths_to_check)
299
+ end
300
+ when Language::Nodes::FragmentSpread
301
+ fragment_defn = query.document.definitions.find { |defn| defn.is_a?(Language::Nodes::FragmentDefinition) && defn.name == ast_selection.name }
302
+ static_type_at_result = @static_type_at[result_h]
303
+ if static_type_at_result && type_condition_applies?(query.context, static_type_at_result, fragment_defn.type.name)
304
+ result_h = check_object_result(query, result_h, static_type, fragment_defn.selections, current_exec_path, current_result_path, paths_to_check)
305
+ end
306
+ end
307
+ end
308
+
309
+ result_h
310
+ end
311
+
312
+ def check_list_result(query, result_arr, inner_type, ast_selections, current_exec_path, current_result_path, paths_to_check)
313
+ inner_type_non_null = false
314
+ if inner_type.non_null?
315
+ inner_type_non_null = true
316
+ inner_type = inner_type.of_type
317
+ end
318
+
319
+ new_invalid_null = false
320
+ result_arr.map!.with_index do |result_item, idx|
321
+ current_result_path << idx
322
+ new_result = if result_item.is_a?(GraphQL::Error)
323
+ result_item.path = current_result_path.dup
324
+ nil
325
+ elsif inner_type.list?
326
+ check_list_result(query, result_item, inner_type.of_type, ast_selections, current_exec_path, current_result_path, paths_to_check)
327
+ elsif inner_type.kind.leaf?
328
+ result_item
329
+ else
330
+ check_object_result(query, result_item, inner_type, ast_selections, current_exec_path, current_result_path, paths_to_check)
331
+ end
332
+
333
+ if new_result.nil? && inner_type_non_null
334
+ new_invalid_null = true
335
+ nil
336
+ else
337
+ new_result
338
+ end
339
+ ensure
340
+ current_result_path.pop
341
+ end
342
+
343
+ if new_invalid_null
344
+ nil
345
+ else
346
+ result_arr
347
+ end
348
+ end
349
+
350
+ def dir_arg_value(query, arg_node)
351
+ if arg_node.value.is_a?(Language::Nodes::VariableIdentifier)
352
+ var_key = arg_node.value.name
353
+ if query.variables.key?(var_key)
354
+ query.variables[var_key]
355
+ else
356
+ query.variables[var_key.to_sym]
357
+ end
358
+ else
359
+ arg_node.value
360
+ end
361
+ end
362
+ def directives_include?(query, ast_selection)
363
+ if ast_selection.directives.any? { |dir_node|
364
+ if dir_node.name == "skip"
365
+ dir_node.arguments.any? { |arg_node| arg_node.name == "if" && dir_arg_value(query, arg_node) == true } # rubocop:disable Development/ContextIsPassedCop
366
+ elsif dir_node.name == "include"
367
+ dir_node.arguments.any? { |arg_node| arg_node.name == "if" && dir_arg_value(query, arg_node) == false } # rubocop:disable Development/ContextIsPassedCop
368
+ end
369
+ }
370
+ false
371
+ else
372
+ true
373
+ end
374
+ end
375
+
376
+ def type_condition_applies?(context, concrete_type, type_name)
377
+ if type_name == concrete_type.graphql_name
378
+ true
379
+ else
380
+ abs_t = @schema.get_type(type_name, context)
381
+ p_types = @schema.possible_types(abs_t, context)
382
+ c_p_types = @schema.possible_types(concrete_type, context)
383
+ p_types.any? { |t| c_p_types.include?(t) }
384
+ end
385
+ end
386
+ end
387
+ end
388
+ end
389
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ module Next
5
+ class SelectionsStep
6
+ def initialize(parent_type:, selections:, objects:, results:, runner:, query:, path:)
7
+ @path = path
8
+ @parent_type = parent_type
9
+ @selections = selections
10
+ @runner = runner
11
+ @objects = objects
12
+ @results = results
13
+ @query = query
14
+ @graphql_objects = nil
15
+ end
16
+
17
+ attr_reader :path, :query, :objects, :results
18
+
19
+ def graphql_objects
20
+ @graphql_objects ||= @objects.map do |obj|
21
+ @parent_type.scoped_new(obj, @query.context)
22
+ end
23
+ end
24
+
25
+ def call
26
+ grouped_selections = {}
27
+ prototype_result = @results.first
28
+ @runner.gather_selections(@parent_type, @selections, self, self.query, prototype_result, into: grouped_selections)
29
+ @results.each { |r| r.replace(prototype_result) }
30
+ grouped_selections.each_value do |frs|
31
+ @runner.add_step(frs)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+ require "graphql/execution/next/prepare_object_step"
3
+ require "graphql/execution/next/field_resolve_step"
4
+ require "graphql/execution/next/load_argument_step"
5
+ require "graphql/execution/next/runner"
6
+ require "graphql/execution/next/selections_step"
7
+ module GraphQL
8
+ module Execution
9
+ module Next
10
+ module SchemaExtension
11
+ def execute_next(query_str = nil, context: nil, document: nil, variables: nil, root_value: nil, validate: true, visibility_profile: nil)
12
+ multiplex_context = if context
13
+ {
14
+ backtrace: context[:backtrace],
15
+ tracers: context[:tracers],
16
+ trace: context[:trace],
17
+ dataloader: context[:dataloader],
18
+ trace_mode: context[:trace_mode],
19
+ }
20
+ else
21
+ {}
22
+ end
23
+ query_opts = {
24
+ query: query_str,
25
+ document: document,
26
+ context: context,
27
+ validate: validate,
28
+ variables: variables,
29
+ root_value: root_value,
30
+ visibility_profile: visibility_profile,
31
+ }
32
+ m_results = multiplex_next([query_opts], context: multiplex_context, max_complexity: nil)
33
+ m_results[0]
34
+ end
35
+
36
+ def multiplex_next(query_options, context: {}, max_complexity: self.max_complexity)
37
+ Next.run_all(self, query_options, context: context, max_complexity: max_complexity)
38
+ end
39
+
40
+ def execution_next_options
41
+ @execution_next_options || find_inherited_value(:execution_next_options, EmptyObjects::EMPTY_HASH)
42
+ end
43
+
44
+ attr_writer :execution_next_options
45
+ end
46
+
47
+ def self.use(schema, authorization: true)
48
+ schema.extend(SchemaExtension)
49
+ schema.execution_next_options = { authorization: authorization }
50
+ end
51
+
52
+ def self.run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
53
+ queries = query_options.map do |opts|
54
+ case opts
55
+ when Hash
56
+ schema.query_class.new(schema, nil, **opts)
57
+ when GraphQL::Query, GraphQL::Query::Partial
58
+ opts
59
+ else
60
+ raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})"
61
+ end
62
+ end
63
+ multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
64
+ runner = Runner.new(multiplex, **schema.execution_next_options)
65
+ runner.execute
66
+ end
67
+ end
68
+ end
69
+ end
@@ -4,6 +4,7 @@ require "graphql/execution/interpreter"
4
4
  require "graphql/execution/lazy"
5
5
  require "graphql/execution/lookahead"
6
6
  require "graphql/execution/multiplex"
7
+ require "graphql/execution/next"
7
8
  require "graphql/execution/errors"
8
9
 
9
10
  module GraphQL