graphql 2.5.22 → 2.5.24

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis.rb +20 -13
  3. data/lib/graphql/execution/field_resolve_step.rb +631 -0
  4. data/lib/graphql/execution/finalize.rb +217 -0
  5. data/lib/graphql/execution/input_values.rb +261 -0
  6. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  7. data/lib/graphql/execution/interpreter/runtime.rb +3 -2
  8. data/lib/graphql/execution/interpreter.rb +6 -9
  9. data/lib/graphql/execution/lazy.rb +1 -1
  10. data/lib/graphql/execution/load_argument_step.rb +64 -0
  11. data/lib/graphql/execution/next.rb +26 -6
  12. data/lib/graphql/execution/prepare_object_step.rb +128 -0
  13. data/lib/graphql/execution/runner.rb +410 -0
  14. data/lib/graphql/execution/selections_step.rb +91 -0
  15. data/lib/graphql/execution.rb +8 -5
  16. data/lib/graphql/execution_error.rb +5 -1
  17. data/lib/graphql/query/context.rb +7 -1
  18. data/lib/graphql/query/partial.rb +18 -3
  19. data/lib/graphql/query.rb +10 -1
  20. data/lib/graphql/runtime_error.rb +6 -0
  21. data/lib/graphql/schema/directive.rb +23 -9
  22. data/lib/graphql/schema/field/connection_extension.rb +2 -15
  23. data/lib/graphql/schema/field/scope_extension.rb +0 -4
  24. data/lib/graphql/schema/field.rb +23 -24
  25. data/lib/graphql/schema/field_extension.rb +11 -41
  26. data/lib/graphql/schema/interface.rb +26 -0
  27. data/lib/graphql/schema/list.rb +5 -1
  28. data/lib/graphql/schema/member/base_dsl_methods.rb +0 -10
  29. data/lib/graphql/schema/member/has_fields.rb +5 -1
  30. data/lib/graphql/schema/non_null.rb +1 -1
  31. data/lib/graphql/schema/resolver.rb +18 -3
  32. data/lib/graphql/schema/subscription.rb +0 -2
  33. data/lib/graphql/schema/visibility/profile.rb +68 -49
  34. data/lib/graphql/schema/wrapper.rb +7 -1
  35. data/lib/graphql/schema.rb +12 -10
  36. data/lib/graphql/static_validation/base_visitor.rb +90 -66
  37. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  38. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  39. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
  40. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
  41. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
  42. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -2
  43. data/lib/graphql/static_validation/rules/fields_will_merge.rb +322 -256
  44. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
  45. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  46. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
  47. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
  48. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
  49. data/lib/graphql/static_validation/validation_context.rb +1 -1
  50. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +34 -10
  51. data/lib/graphql/subscriptions/event.rb +1 -0
  52. data/lib/graphql/subscriptions.rb +35 -0
  53. data/lib/graphql/tracing/perfetto_trace.rb +2 -2
  54. data/lib/graphql/tracing/trace.rb +6 -0
  55. data/lib/graphql/unauthorized_error.rb +4 -0
  56. data/lib/graphql/version.rb +1 -1
  57. data/lib/graphql.rb +1 -3
  58. metadata +11 -8
  59. data/lib/graphql/execution/next/field_resolve_step.rb +0 -711
  60. data/lib/graphql/execution/next/load_argument_step.rb +0 -60
  61. data/lib/graphql/execution/next/prepare_object_step.rb +0 -129
  62. data/lib/graphql/execution/next/runner.rb +0 -389
  63. data/lib/graphql/execution/next/selections_step.rb +0 -37
@@ -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
@@ -0,0 +1,410 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ class Runner
5
+ def initialize(multiplex, authorization:)
6
+ @multiplex = multiplex
7
+ @schema = multiplex.schema
8
+ @steps_queue = []
9
+ @runtime_type_at = {}.compare_by_identity
10
+ @static_type_at = {}.compare_by_identity
11
+ @finalizers = nil
12
+ @selected_operation = nil
13
+ @dataloader = multiplex.context[:dataloader] ||= @schema.dataloader_class.new
14
+ @resolves_lazies = @schema.resolves_lazies?
15
+ @input_values = Hash.new do |h, query|
16
+ h[query] = InputValues.new(query, self)
17
+ end.compare_by_identity
18
+
19
+ @runtime_directives = nil
20
+ @schema.directives.each do |name, dir_class|
21
+ if dir_class.runtime? && name != "include" && name != "skip"
22
+ @runtime_directives ||= {}
23
+ @runtime_directives[dir_class.graphql_name] = dir_class
24
+ end
25
+ end
26
+
27
+ if @runtime_directives.nil?
28
+ @uses_runtime_directives = false
29
+ @runtime_directives = EmptyObjects::EMPTY_HASH
30
+ else
31
+ @uses_runtime_directives = true
32
+ end
33
+
34
+ @lazy_cache = resolves_lazies ? {}.compare_by_identity : nil
35
+ @authorization = authorization
36
+ if @authorization
37
+ @authorizes_cache = Hash.new do |h, query_context|
38
+ h[query_context] = {}.compare_by_identity
39
+ end.compare_by_identity
40
+ end
41
+ end
42
+
43
+ attr_reader :runtime_directives, :uses_runtime_directives, :finalizer_keys
44
+
45
+ def resolve_type(type, object, query)
46
+ query.current_trace.begin_resolve_type(type, object, query.context)
47
+ resolved_type, _ignored_new_value = query.resolve_type(type, object)
48
+ query.current_trace.end_resolve_type(type, object, query.context, resolved_type)
49
+ resolved_type
50
+ end
51
+
52
+ def authorizes?(graphql_definition, query_context)
53
+ auth_cache = @authorizes_cache[query_context]
54
+ case (auth_res = auth_cache[graphql_definition])
55
+ when nil
56
+ auth_cache[graphql_definition] = graphql_definition.authorizes?(query_context)
57
+ else
58
+ auth_res
59
+ end
60
+ end
61
+
62
+ def add_step(step)
63
+ @dataloader.append_job(step)
64
+ end
65
+
66
+ attr_reader :authorization, :steps_queue, :schema, :variables, :dataloader, :resolves_lazies, :authorizes, :static_type_at, :runtime_type_at, :finalizers, :input_values
67
+
68
+ # @return [void]
69
+ def add_finalizer(query, result_value, key, finalizer)
70
+ @finalizers ||= {}.compare_by_identity
71
+ f_for_query = @finalizers[query] ||= {}.compare_by_identity
72
+ f_for_result = f_for_query[result_value] ||= {}.compare_by_identity
73
+ if (f = f_for_result[key])
74
+ if f.is_a?(Array)
75
+ f << finalizer
76
+ else
77
+ f_for_result[key] = [f, finalizer]
78
+ end
79
+ else
80
+ f_for_result[key] = finalizer
81
+ end
82
+ nil
83
+ end
84
+
85
+ def execute
86
+ Fiber[:__graphql_current_multiplex] = @multiplex
87
+ isolated_steps = [[]]
88
+ trace = @multiplex.current_trace
89
+ queries = @multiplex.queries
90
+ multiplex_analyzers = @schema.multiplex_analyzers
91
+ if @multiplex.max_complexity
92
+ multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity]
93
+ end
94
+
95
+ trace.execute_multiplex(multiplex: @multiplex) do
96
+ trace.begin_analyze_multiplex(@multiplex, multiplex_analyzers)
97
+ @schema.analysis_engine.analyze_multiplex(@multiplex, multiplex_analyzers)
98
+ trace.end_analyze_multiplex(@multiplex, multiplex_analyzers)
99
+
100
+ results = []
101
+ queries.each do |query|
102
+ if query.validate && !query.valid?
103
+ results << {
104
+ "errors" => query.static_errors.map(&:to_h)
105
+ }
106
+ next
107
+ end
108
+
109
+ root_type = query.root_type
110
+
111
+ if root_type.non_null?
112
+ root_type = root_type.of_type
113
+ end
114
+
115
+ root_value = query.root_value
116
+ if resolves_lazies
117
+ root_value = schema.sync_lazy(root_value)
118
+ end
119
+
120
+ trace.execute_query(query: query) do
121
+ begin_execute(isolated_steps, results, query, root_type, root_value)
122
+ end
123
+ end
124
+
125
+ trace.execute_query_lazy(query: nil, multiplex: @multiplex) do
126
+ while (next_isolated_steps = isolated_steps.shift)
127
+ next_isolated_steps.each do |step|
128
+ add_step(step)
129
+ end
130
+ @dataloader.run
131
+ end
132
+ end
133
+
134
+ queries.each_with_index.map do |query, idx|
135
+ result = results[idx]
136
+
137
+ fin_result = if (!@finalizers&.key?(query) && query.context.errors.empty?) || !query.valid?
138
+ result
139
+ else
140
+ data = result["data"]
141
+ data = Finalize.new(query, data, self).run
142
+ errors = []
143
+ query.context.errors.each do |err|
144
+ if err.respond_to?(:to_h)
145
+ errors << err.to_h
146
+ end
147
+ end
148
+ res_h = {}
149
+ if !errors.empty?
150
+ res_h["errors"] = errors
151
+ end
152
+ res_h["data"] = data
153
+ res_h
154
+ end
155
+
156
+ query.result_values = fin_result
157
+ query.result
158
+ end
159
+ end
160
+ ensure
161
+ Fiber[:__graphql_current_multiplex] = nil
162
+ end
163
+
164
+ def gather_selections(type_defn, ast_selections, selections_step, query, all_selections, prototype_result, into:)
165
+ ast_selections.each do |ast_selection|
166
+ next if !directives_include?(query, ast_selection)
167
+
168
+ case ast_selection
169
+ when GraphQL::Language::Nodes::Field
170
+ key = ast_selection.alias || ast_selection.name
171
+ step = into[key] ||= begin
172
+ prototype_result[key] = nil
173
+
174
+ FieldResolveStep.new(
175
+ selections_step: selections_step,
176
+ key: key,
177
+ parent_type: type_defn,
178
+ runner: self,
179
+ )
180
+ end
181
+ step.append_selection(ast_selection)
182
+ when GraphQL::Language::Nodes::InlineFragment
183
+ type_condition = ast_selection.type&.name
184
+ if type_condition.nil? || type_condition_applies?(query.context, type_defn, type_condition)
185
+ if uses_runtime_directives && !ast_selection.directives.empty?
186
+ all_selections << (into = { __node: ast_selection })
187
+ all_selections << (prototype_result = {})
188
+ end
189
+ gather_selections(type_defn, ast_selection.selections, selections_step, query, all_selections, prototype_result, into: into)
190
+ end
191
+ when GraphQL::Language::Nodes::FragmentSpread
192
+ fragment_definition = query.fragments[ast_selection.name]
193
+ type_condition = fragment_definition.type.name
194
+ if type_condition_applies?(query.context, type_defn, type_condition)
195
+ if uses_runtime_directives && !ast_selection.directives.empty?
196
+ all_selections << (into = { __node: ast_selection })
197
+ all_selections << (prototype_result = {})
198
+ end
199
+ gather_selections(type_defn, fragment_definition.selections, selections_step, query, all_selections, prototype_result, into: into)
200
+ end
201
+ else
202
+ raise ArgumentError, "Unsupported graphql selection node: #{ast_selection.class} (#{ast_selection.inspect})"
203
+ end
204
+ end
205
+ end
206
+
207
+ def lazy?(object)
208
+ obj_class = object.class
209
+ is_lazy = @lazy_cache[obj_class]
210
+ if is_lazy.nil?
211
+ is_lazy = @lazy_cache[obj_class] = @schema.lazy?(object)
212
+ end
213
+ is_lazy
214
+ end
215
+
216
+ def type_condition_applies?(context, concrete_type, type_name)
217
+ if type_name == concrete_type.graphql_name
218
+ true
219
+ else
220
+ abs_t = @schema.get_type(type_name, context)
221
+ p_types = @schema.possible_types(abs_t, context)
222
+ c_p_types = @schema.possible_types(concrete_type, context)
223
+ p_types.any? { |t| c_p_types.include?(t) }
224
+ end
225
+ end
226
+
227
+ private
228
+
229
+ def begin_execute(isolated_steps, results, query, root_type, root_value)
230
+ data = {}
231
+ selected_operation = query.selected_operation
232
+ beginning_path = query.path
233
+
234
+ case root_type.kind.name
235
+ when "OBJECT"
236
+ if self.authorization && authorizes?(root_type, query.context)
237
+ query.current_trace.begin_authorized(root_type, root_value, query.context)
238
+ auth_check = schema.sync_lazy(root_type.authorized?(root_value, query.context))
239
+ query.current_trace.end_authorized(root_type, root_value, query.context, auth_check)
240
+ root_value = if auth_check
241
+ root_value
242
+ else
243
+ begin
244
+ auth_err = GraphQL::UnauthorizedError.new(object: root_value, type: root_type, context: query.context)
245
+ new_val = schema.unauthorized_object(auth_err)
246
+ if new_val
247
+ auth_check = true
248
+ end
249
+ new_val
250
+ rescue GraphQL::ExecutionError => ex_err
251
+ # The old runtime didn't add path and ast_nodes to this
252
+ ex_err.path = beginning_path
253
+ query.context.add_error(ex_err)
254
+ nil
255
+ end
256
+ end
257
+
258
+ if !auth_check
259
+ results << {}
260
+ return
261
+ end
262
+ end
263
+
264
+ results << { "data" => data }
265
+ objects = [root_value]
266
+ query.current_trace.objects(root_type, objects, query.context)
267
+
268
+ if query.query?
269
+ isolated_steps[0] << SelectionsStep.new(
270
+ parent_type: root_type,
271
+ selections: query.selected_operation.selections,
272
+ objects: objects,
273
+ results: [data],
274
+ path: beginning_path,
275
+ runner: self,
276
+ query: query,
277
+ )
278
+ elsif query.mutation?
279
+ fields = {}
280
+ all_selections = [fields, (prototype_result = {})]
281
+ gather_selections(root_type, selected_operation.selections, nil, query, all_selections, prototype_result, into: fields)
282
+ if all_selections.length > 2
283
+ # TODO DRY with SelectionsStep with directive handling
284
+ raise "Directives on root mutation type not implemented yet"
285
+ end
286
+ fields.each_value do |field_resolve_step|
287
+ isolated_steps << [SelectionsStep.new(
288
+ clobber: false, # `data` is being shared among several selections steps
289
+ parent_type: root_type,
290
+ selections: field_resolve_step.ast_nodes || Array(field_resolve_step.ast_node),
291
+ objects: objects,
292
+ results: [data],
293
+ path: beginning_path,
294
+ runner: self,
295
+ query: query,
296
+ )]
297
+ end
298
+ elsif query.subscription?
299
+ if !query.subscription_update?
300
+ schema.subscriptions.initialize_subscriptions(query)
301
+ add_finalizer(query, data, nil, schema.subscriptions.finalizer)
302
+ end
303
+ isolated_steps[0] << SelectionsStep.new(
304
+ parent_type: root_type,
305
+ selections: selected_operation.selections,
306
+ objects: objects,
307
+ results: [data],
308
+ path: beginning_path,
309
+ runner: self,
310
+ query: query,
311
+ )
312
+ else
313
+ raise ArgumentError, "Unknown operation type (not query, mutation or subscription): #{query.query_string}"
314
+ end
315
+ when "UNION", "INTERFACE"
316
+ resolved_type = resolve_type(root_type, root_value, query)
317
+ if resolves_lazies
318
+ resolved_type = schema.sync_lazy(resolved_type)
319
+ end
320
+ objects = [root_value]
321
+ query.current_trace.objects(resolved_type, objects, query.context)
322
+ runtime_type_at[data] = resolved_type
323
+ results << { "data" => data }
324
+ isolated_steps[0] << SelectionsStep.new(
325
+ parent_type: resolved_type,
326
+ selections: query.selected_operation.selections,
327
+ objects: objects,
328
+ results: [data],
329
+ path: beginning_path,
330
+ runner: self,
331
+ query: query,
332
+ )
333
+ when "LIST"
334
+ inner_type = root_type.unwrap
335
+ case inner_type.kind.name
336
+ when "SCALAR", "ENUM"
337
+ results << run_isolated_scalar(root_type, query)
338
+ else
339
+ list_result = Array.new(root_value.size) { Hash.new.compare_by_identity }
340
+ results << { "data" => list_result }
341
+ isolated_steps[0] << SelectionsStep.new(
342
+ parent_type: inner_type,
343
+ selections: query.selected_operation.selections,
344
+ objects: root_value,
345
+ results: list_result,
346
+ path: beginning_path,
347
+ runner: self,
348
+ query: query,
349
+ )
350
+ end
351
+ when "SCALAR", "ENUM"
352
+ results << run_isolated_scalar(root_type, query)
353
+ else
354
+ raise "Unhandled root type kind: #{root_type.kind.name.inspect}"
355
+ end
356
+
357
+ @static_type_at[data] = root_type
358
+ end
359
+
360
+ def directives_include?(query, ast_selection)
361
+ if ast_selection.directives.any? { |dir_node|
362
+ if dir_node.name == "skip"
363
+ skip_args = @input_values[query].argument_values(GraphQL::Schema::Directive::Skip, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
364
+ skip_args[:if] == true
365
+ elsif dir_node.name == "include"
366
+ include_args = @input_values[query].argument_values(GraphQL::Schema::Directive::Include, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
367
+ include_args[:if] == false
368
+ end
369
+ }
370
+ false
371
+ else
372
+ true
373
+ end
374
+ end
375
+
376
+ def run_isolated_scalar(type, partial)
377
+ value = partial.root_value
378
+ dummy_path = partial.path.dup
379
+ key = dummy_path.pop
380
+ is_from_array = key.is_a?(Integer)
381
+
382
+ if lazy?(value)
383
+ value = @schema.sync_lazy(value)
384
+ end
385
+ selections = partial.ast_nodes
386
+ dummy_ss = SelectionsStep.new(
387
+ parent_type: nil,
388
+ selections: selections,
389
+ objects: nil,
390
+ results: nil,
391
+ path: dummy_path,
392
+ runner: self,
393
+ query: partial,
394
+ )
395
+ dummy_frs = FieldResolveStep.new(
396
+ selections_step: dummy_ss,
397
+ key: key,
398
+ parent_type: nil,
399
+ runner: self,
400
+ )
401
+ dummy_frs.static_type = type
402
+ selections.each { |s| dummy_frs.append_selection(s) }
403
+
404
+ result = is_from_array ? [] : {}
405
+ dummy_frs.finish_leaf_result(result, key, value, type, partial.context)
406
+ { "data" => result[key] }
407
+ end
408
+ end
409
+ end
410
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ class SelectionsStep
5
+ def initialize(parent_type:, selections:, objects:, results:, runner:, query:, path:, clobber: true)
6
+ @path = path
7
+ @parent_type = parent_type
8
+ @selections = selections
9
+ @runner = runner
10
+ @objects = objects
11
+ @results = results
12
+ @query = query
13
+ @graphql_objects = nil
14
+ @all_selections = nil
15
+ @clobber = clobber
16
+ end
17
+
18
+ attr_reader :path, :query, :objects, :results
19
+
20
+ def graphql_objects
21
+ @graphql_objects ||= @objects.map do |obj|
22
+ @parent_type.scoped_new(obj, @query.context)
23
+ end
24
+ end
25
+
26
+ def call
27
+ @all_selections = [{}, (prototype_result = {})]
28
+ @runner.gather_selections(@parent_type, @selections, self, self.query, @all_selections, @all_selections[1], into: @all_selections[0])
29
+ continue_selections = []
30
+ i = 0
31
+ l = @all_selections.length
32
+ while i < l
33
+ grouped_selections = @all_selections[i]
34
+ selections_prototype_result = @all_selections[i + 1]
35
+ if (directives_owner = grouped_selections.delete(:__node))
36
+ directives = directives_owner.directives
37
+ continue_execution = true
38
+ directives.each do |dir_node|
39
+ dir_defn = @runner.runtime_directives[dir_node.name]
40
+ if dir_defn # not present for `skip` or `include`
41
+ dir_args = @runner.input_values[query].argument_values(dir_defn, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
42
+ result = case directives_owner
43
+ when Language::Nodes::FragmentSpread
44
+ dir_defn.resolve_fragment_spread(directives_owner, @parent_type, @objects, dir_args, self.query.context)
45
+ when Language::Nodes::InlineFragment
46
+ dir_defn.resolve_inline_fragment(directives_owner, @parent_type, @objects, dir_args, self.query.context)
47
+ else
48
+ raise ArgumentError, "Unhandled directive owner (#{directives_owner.class}): #{directives_owner.inspect}"
49
+ end
50
+ if result.is_a?(Finalizer)
51
+ result.path = path
52
+ @results.each do |r|
53
+ @runner.add_finalizer(@query, r, nil, result)
54
+ end
55
+ if result.is_a?(HaltExecution)
56
+ continue_execution = false
57
+ break
58
+ end
59
+ end
60
+
61
+ if continue_execution
62
+ prototype_result.merge!(selections_prototype_result)
63
+ grouped_selections.each_value { |v| continue_selections << v }
64
+ end
65
+ else
66
+ grouped_selections.each_value { |v| continue_selections << v }
67
+ end
68
+ end
69
+ else
70
+ grouped_selections.each_value { |v| continue_selections << v }
71
+ end
72
+
73
+ if @clobber
74
+ i2 = 0
75
+ l2 = @results.length
76
+ while i2 < l2
77
+ @results[i2].replace(prototype_result)
78
+ i2 += 1
79
+ end
80
+ end
81
+
82
+ continue_selections.each do |frs|
83
+ @runner.add_step(frs)
84
+ end
85
+
86
+ i += 2
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -1,19 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/execution/directive_checks"
3
+ require "graphql/execution/next"
3
4
  require "graphql/execution/interpreter"
4
5
  require "graphql/execution/lazy"
5
6
  require "graphql/execution/lookahead"
6
7
  require "graphql/execution/multiplex"
7
- require "graphql/execution/next"
8
8
  require "graphql/execution/errors"
9
9
 
10
10
  module GraphQL
11
11
  module Execution
12
12
  # @api private
13
- class Skip < GraphQL::Error; end
13
+ class Skip < GraphQL::RuntimeError
14
+ attr_accessor :path
15
+ def ast_nodes=(_ignored); end
14
16
 
15
- # Just a singleton for implementing {Query::Context#skip}
16
- # @api private
17
- SKIP = Skip.new
17
+ def finalize_graphql_result(query, result_data, key)
18
+ result_data.delete(key)
19
+ end
20
+ end
18
21
  end
19
22
  end
@@ -6,7 +6,7 @@ module GraphQL
6
6
  class ExecutionError < GraphQL::RuntimeError
7
7
  # @return [GraphQL::Language::Nodes::Field] the field where the error occurred
8
8
  def ast_node
9
- ast_nodes.first
9
+ ast_nodes&.first
10
10
  end
11
11
 
12
12
  def ast_node=(new_node)
@@ -36,6 +36,10 @@ module GraphQL
36
36
  super(message)
37
37
  end
38
38
 
39
+ def finalize_graphql_result(query, result_data, key)
40
+ result_data[key] = nil
41
+ end
42
+
39
43
  # @return [Hash] An entry for the response's "errors" key
40
44
  def to_h
41
45
  hash = {
@@ -112,7 +112,7 @@ module GraphQL
112
112
  # Return this value to tell the runtime
113
113
  # to exclude this field from the response altogether
114
114
  def skip
115
- GraphQL::Execution::SKIP
115
+ GraphQL::Execution::Skip.new
116
116
  end
117
117
 
118
118
  # Add error at query-level.
@@ -126,6 +126,12 @@ module GraphQL
126
126
  nil
127
127
  end
128
128
 
129
+ # @param value [Object] Any object to be inserted directly into the final response
130
+ # @return [GraphQL::Execution::Interpreter::RawValue] Return this from the field
131
+ def raw_value(value)
132
+ GraphQL::Execution::Interpreter::RawValue.new(value)
133
+ end
134
+
129
135
  # @example Print the GraphQL backtrace during field resolution
130
136
  # puts ctx.backtrace
131
137
  #