graphql 2.5.23 → 2.6.3

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/query_complexity.rb +29 -13
  3. data/lib/graphql/analysis.rb +20 -13
  4. data/lib/graphql/backtrace/table.rb +10 -1
  5. data/lib/graphql/current.rb +7 -1
  6. data/lib/graphql/dataloader.rb +1 -1
  7. data/lib/graphql/execution/directive_checks.rb +2 -0
  8. data/lib/graphql/execution/field_resolve_step.rb +744 -0
  9. data/lib/graphql/execution/finalize.rb +230 -0
  10. data/lib/graphql/execution/input_values.rb +333 -0
  11. data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -0
  12. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  13. data/lib/graphql/execution/interpreter/runtime.rb +36 -15
  14. data/lib/graphql/execution/load_argument_step.rb +102 -0
  15. data/lib/graphql/execution/next.rb +42 -16
  16. data/lib/graphql/execution/prepare_object_step.rb +147 -0
  17. data/lib/graphql/execution/resolve_type_step.rb +27 -0
  18. data/lib/graphql/execution/runner.rb +445 -0
  19. data/lib/graphql/execution/selections_step.rb +91 -0
  20. data/lib/graphql/execution.rb +10 -3
  21. data/lib/graphql/execution_error.rb +7 -13
  22. data/lib/graphql/introspection/entry_points.rb +2 -2
  23. data/lib/graphql/introspection/schema_type.rb +6 -2
  24. data/lib/graphql/language/lexer.rb +12 -8
  25. data/lib/graphql/language/parser.rb +1 -1
  26. data/lib/graphql/language.rb +8 -2
  27. data/lib/graphql/pagination/connections.rb +1 -3
  28. data/lib/graphql/query/context.rb +6 -0
  29. data/lib/graphql/query/partial.rb +18 -3
  30. data/lib/graphql/query.rb +12 -3
  31. data/lib/graphql/runtime_error.rb +6 -0
  32. data/lib/graphql/schema/argument.rb +3 -3
  33. data/lib/graphql/schema/build_from_definition.rb +10 -0
  34. data/lib/graphql/schema/directive/feature.rb +4 -0
  35. data/lib/graphql/schema/directive/transform.rb +20 -0
  36. data/lib/graphql/schema/directive.rb +23 -9
  37. data/lib/graphql/schema/field/connection_extension.rb +2 -15
  38. data/lib/graphql/schema/field/scope_extension.rb +0 -4
  39. data/lib/graphql/schema/field.rb +20 -20
  40. data/lib/graphql/schema/field_extension.rb +11 -41
  41. data/lib/graphql/schema/has_single_input_argument.rb +24 -13
  42. data/lib/graphql/schema/input_object.rb +4 -0
  43. data/lib/graphql/schema/interface.rb +26 -0
  44. data/lib/graphql/schema/introspection_system.rb +6 -21
  45. data/lib/graphql/schema/list.rb +4 -0
  46. data/lib/graphql/schema/member/base_dsl_methods.rb +0 -10
  47. data/lib/graphql/schema/printer.rb +1 -1
  48. data/lib/graphql/schema/ractor_shareable.rb +1 -0
  49. data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
  50. data/lib/graphql/schema/resolver.rb +30 -14
  51. data/lib/graphql/schema/subscription.rb +53 -8
  52. data/lib/graphql/schema/timeout.rb +2 -2
  53. data/lib/graphql/schema/validator/allow_blank_validator.rb +3 -3
  54. data/lib/graphql/schema/validator/allow_null_validator.rb +3 -3
  55. data/lib/graphql/schema/validator/exclusion_validator.rb +2 -2
  56. data/lib/graphql/schema/validator/format_validator.rb +3 -3
  57. data/lib/graphql/schema/validator/inclusion_validator.rb +2 -2
  58. data/lib/graphql/schema/validator/length_validator.rb +6 -6
  59. data/lib/graphql/schema/validator/numericality_validator.rb +19 -19
  60. data/lib/graphql/schema/validator/required_validator.rb +6 -4
  61. data/lib/graphql/schema/validator.rb +9 -0
  62. data/lib/graphql/schema/visibility/profile.rb +6 -4
  63. data/lib/graphql/schema/visibility/visit.rb +1 -1
  64. data/lib/graphql/schema/visibility.rb +30 -22
  65. data/lib/graphql/schema.rb +43 -20
  66. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +31 -25
  67. data/lib/graphql/subscriptions/event.rb +0 -1
  68. data/lib/graphql/subscriptions.rb +15 -0
  69. data/lib/graphql/tracing/perfetto_trace.rb +5 -3
  70. data/lib/graphql/tracing/trace.rb +6 -0
  71. data/lib/graphql/unauthorized_error.rb +1 -1
  72. data/lib/graphql/version.rb +1 -1
  73. data/lib/graphql.rb +1 -3
  74. metadata +11 -7
  75. data/lib/graphql/execution/next/field_resolve_step.rb +0 -743
  76. data/lib/graphql/execution/next/load_argument_step.rb +0 -64
  77. data/lib/graphql/execution/next/prepare_object_step.rb +0 -129
  78. data/lib/graphql/execution/next/runner.rb +0 -411
  79. data/lib/graphql/execution/next/selections_step.rb +0 -37
@@ -0,0 +1,445 @@
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
+ @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
+ @authorizes_cache = Hash.new do |h, query_context|
36
+ h[query_context] = {}.compare_by_identity
37
+ end.compare_by_identity
38
+ end
39
+
40
+ attr_reader :runtime_directives, :uses_runtime_directives, :finalizer_keys
41
+
42
+ def authorizes?(graphql_definition, query_context)
43
+ auth_cache = @authorizes_cache[query_context]
44
+ case (auth_res = auth_cache[graphql_definition])
45
+ when nil
46
+ auth_cache[graphql_definition] = graphql_definition.authorizes?(query_context)
47
+ else
48
+ auth_res
49
+ end
50
+ end
51
+
52
+ def add_step(step)
53
+ @dataloader.append_job(step)
54
+ end
55
+
56
+ attr_reader :steps_queue, :schema, :variables, :dataloader, :resolves_lazies, :authorizes, :static_type_at, :runtime_type_at, :finalizers, :input_values
57
+
58
+ # @return [void]
59
+ def add_finalizer(query, result_value, key, finalizer)
60
+ @finalizers ||= {}.compare_by_identity
61
+ f_for_query = @finalizers[query] ||= {}.compare_by_identity
62
+ f_for_result = f_for_query[result_value] ||= {}.compare_by_identity
63
+ if (f = f_for_result[key])
64
+ if f.is_a?(Array)
65
+ f << finalizer
66
+ else
67
+ f_for_result[key] = [f, finalizer]
68
+ end
69
+ else
70
+ f_for_result[key] = finalizer
71
+ end
72
+ nil
73
+ end
74
+
75
+ def execute
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
+ ensure
160
+ Fiber[:__graphql_current_multiplex] = nil
161
+ end
162
+
163
+ def gather_selections(type_defn, ast_selections, selections_step, query, all_selections, prototype_result, into:)
164
+ ast_selections.each do |ast_selection|
165
+ next if !directives_include?(query, ast_selection)
166
+
167
+ case ast_selection
168
+ when GraphQL::Language::Nodes::Field
169
+ key = ast_selection.alias || ast_selection.name
170
+ step = into[key] ||= begin
171
+ prototype_result[key] = nil
172
+
173
+ FieldResolveStep.new(
174
+ selections_step: selections_step,
175
+ key: key,
176
+ parent_type: type_defn,
177
+ runner: self,
178
+ )
179
+ end
180
+ step.append_selection(ast_selection)
181
+ when GraphQL::Language::Nodes::InlineFragment
182
+ type_condition = ast_selection.type&.name
183
+ if type_condition.nil? || type_condition_applies?(query.context, type_defn, type_condition)
184
+ if uses_runtime_directives && !ast_selection.directives.empty?
185
+ all_selections << (into = { __node: ast_selection })
186
+ all_selections << (prototype_result = {})
187
+ end
188
+ gather_selections(type_defn, ast_selection.selections, selections_step, query, all_selections, prototype_result, into: into)
189
+ end
190
+ when GraphQL::Language::Nodes::FragmentSpread
191
+ fragment_definition = query.fragments[ast_selection.name]
192
+ type_condition = fragment_definition.type.name
193
+ if type_condition_applies?(query.context, type_defn, type_condition)
194
+ if uses_runtime_directives && !ast_selection.directives.empty?
195
+ all_selections << (into = { __node: ast_selection })
196
+ all_selections << (prototype_result = {})
197
+ end
198
+ gather_selections(type_defn, fragment_definition.selections, selections_step, query, all_selections, prototype_result, into: into)
199
+ end
200
+ else
201
+ raise ArgumentError, "Unsupported graphql selection node: #{ast_selection.class} (#{ast_selection.inspect})"
202
+ end
203
+ end
204
+ end
205
+
206
+ def lazy?(object)
207
+ obj_class = object.class
208
+ is_lazy = @lazy_cache[obj_class]
209
+ if is_lazy.nil?
210
+ is_lazy = @lazy_cache[obj_class] = @schema.lazy?(object)
211
+ end
212
+ is_lazy
213
+ end
214
+
215
+ def type_condition_applies?(context, concrete_type, type_name)
216
+ if type_name == concrete_type.graphql_name
217
+ true
218
+ else
219
+ abs_t = @schema.get_type(type_name, context)
220
+ p_types = @schema.possible_types(abs_t, context)
221
+ c_p_types = @schema.possible_types(concrete_type, context)
222
+ p_types.any? { |t| c_p_types.include?(t) }
223
+ end
224
+ end
225
+
226
+ private
227
+
228
+ def begin_execute(isolated_steps, results, query, root_type, root_value)
229
+ data = {}
230
+ @static_type_at[data] = root_type
231
+ selected_operation = query.selected_operation
232
+ beginning_path = query.path
233
+
234
+ case root_type.kind.name
235
+ when "OBJECT"
236
+ if 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.is_a?(GraphQL::Query) && uses_runtime_directives && (query_dirs = selected_operation.directives).any? # rubocop:disable Development/NoneWithoutBlockCop
269
+ continue_execution = true
270
+ query_dirs.each do |dir_node|
271
+ dir_defn = runtime_directives[dir_node.name] || raise(GraphQL::Error, "No directive definition found for: #{dir_node.name.inspect}")
272
+ dir_args, errors = input_values[query].argument_values(dir_defn, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
273
+ if errors
274
+ errors.each { |e|
275
+ e.ast_node = dir_node
276
+ e.path = beginning_path
277
+ query.context.add_error(e)
278
+ }
279
+ continue_execution = false
280
+ break
281
+ end
282
+ result = dir_defn.resolve_operation(selected_operation, query, objects, dir_args, query.context)
283
+ if result.is_a?(Finalizer)
284
+ result.path = path
285
+ add_finalizer(query, result, nil, data)
286
+ if result.is_a?(HaltExecution)
287
+ continue_execution = false
288
+ break
289
+ end
290
+ end
291
+ end
292
+
293
+ if !continue_execution
294
+ return
295
+ end
296
+ end
297
+
298
+ if query.query?
299
+ isolated_steps[0] << SelectionsStep.new(
300
+ parent_type: root_type,
301
+ selections: selected_operation.selections,
302
+ objects: objects,
303
+ results: [data],
304
+ path: beginning_path,
305
+ runner: self,
306
+ query: query,
307
+ )
308
+ elsif query.mutation?
309
+ fields = {}
310
+ all_selections = [fields, (prototype_result = {})]
311
+ gather_selections(root_type, selected_operation.selections, nil, query, all_selections, prototype_result, into: fields)
312
+ if all_selections.length > 2
313
+ # TODO DRY with SelectionsStep with directive handling
314
+ raise "Directives on root mutation type not implemented yet"
315
+ end
316
+ fields.each_value do |field_resolve_step|
317
+ isolated_steps << [SelectionsStep.new(
318
+ clobber: false, # `data` is being shared among several selections steps
319
+ parent_type: root_type,
320
+ selections: field_resolve_step.ast_nodes || Array(field_resolve_step.ast_node),
321
+ objects: objects,
322
+ results: [data],
323
+ path: beginning_path,
324
+ runner: self,
325
+ query: query,
326
+ )]
327
+ end
328
+ elsif query.subscription?
329
+ if !query.subscription_update?
330
+ schema.subscriptions.initialize_subscriptions(query)
331
+ add_finalizer(query, data, nil, schema.subscriptions.finalizer)
332
+ end
333
+ isolated_steps[0] << SelectionsStep.new(
334
+ parent_type: root_type,
335
+ selections: selected_operation.selections,
336
+ objects: objects,
337
+ results: [data],
338
+ path: beginning_path,
339
+ runner: self,
340
+ query: query,
341
+ )
342
+ else
343
+ raise ArgumentError, "Unknown operation type (not query, mutation or subscription): #{query.query_string}"
344
+ end
345
+ when "UNION", "INTERFACE"
346
+ resolved_type = ResolveTypeStep.resolve_type(root_type, root_value, query)
347
+ if resolves_lazies && lazy?(resolved_type)
348
+ resolved_type = schema.sync_lazy(resolved_type)
349
+ end
350
+ resolved_type, root_value = resolved_type
351
+ ResolveTypeStep.assert_valid_resolved_type(root_type, resolved_type, root_value, nil, query: query)
352
+ objects = [root_value]
353
+ query.current_trace.objects(resolved_type, objects, query.context)
354
+ runtime_type_at[data] = resolved_type
355
+ results << { "data" => data }
356
+ isolated_steps[0] << SelectionsStep.new(
357
+ parent_type: resolved_type,
358
+ selections: selected_operation.selections,
359
+ objects: objects,
360
+ results: [data],
361
+ path: beginning_path,
362
+ runner: self,
363
+ query: query,
364
+ )
365
+ when "LIST"
366
+ inner_type = root_type.unwrap
367
+ case inner_type.kind.name
368
+ when "SCALAR", "ENUM"
369
+ results << run_isolated_scalar(root_type, query)
370
+ else
371
+ list_result = Array.new(root_value.size) { Hash.new.compare_by_identity }
372
+ results << { "data" => list_result }
373
+ isolated_steps[0] << SelectionsStep.new(
374
+ parent_type: inner_type,
375
+ selections: selected_operation.selections,
376
+ objects: root_value,
377
+ results: list_result,
378
+ path: beginning_path,
379
+ runner: self,
380
+ query: query,
381
+ )
382
+ end
383
+ when "SCALAR", "ENUM"
384
+ results << run_isolated_scalar(root_type, query)
385
+ else
386
+ raise "Unhandled root type kind: #{root_type.kind.name.inspect}"
387
+ end
388
+ end
389
+
390
+ def directives_include?(query, ast_selection)
391
+ if ast_selection.directives.any? { |dir_node|
392
+ case dir_node.name
393
+ when "skip"
394
+ skip_args, _errors = @input_values[query].argument_values(GraphQL::Schema::Directive::Skip, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
395
+ skip_args[:if] == true
396
+ when "include"
397
+ include_args, _errors = @input_values[query].argument_values(GraphQL::Schema::Directive::Include, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
398
+ include_args[:if] == false
399
+ else
400
+ dir_defn = runtime_directives[dir_node.name]
401
+ dir_args, _errors = @input_values[query].argument_values(dir_defn, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
402
+ !dir_defn.include?(nil, dir_args, query.context)
403
+ end
404
+ }
405
+ false
406
+ else
407
+ true
408
+ end
409
+ end
410
+
411
+ def run_isolated_scalar(type, partial)
412
+ value = partial.root_value
413
+ dummy_path = partial.path.dup
414
+ key = dummy_path.pop
415
+ is_from_array = key.is_a?(Integer)
416
+
417
+ if lazy?(value)
418
+ value = @schema.sync_lazy(value)
419
+ end
420
+ selections = partial.ast_nodes
421
+ dummy_ss = SelectionsStep.new(
422
+ parent_type: nil,
423
+ selections: selections,
424
+ objects: nil,
425
+ results: nil,
426
+ path: dummy_path,
427
+ runner: self,
428
+ query: partial,
429
+ )
430
+ dummy_frs = FieldResolveStep.new(
431
+ selections_step: dummy_ss,
432
+ key: key,
433
+ parent_type: nil,
434
+ runner: self,
435
+ )
436
+ dummy_frs.static_type = type
437
+ selections.each { |s| dummy_frs.append_selection(s) }
438
+
439
+ result = is_from_array ? [] : {}
440
+ dummy_frs.finish_leaf_result(result, key, value, type, partial.context)
441
+ { "data" => result[key] }
442
+ end
443
+ end
444
+ end
445
+ 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, _errors = @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,10 +1,10 @@
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
@@ -14,8 +14,15 @@ module GraphQL
14
14
  attr_accessor :path
15
15
  def ast_nodes=(_ignored); end
16
16
 
17
- def assign_graphql_result(query, result_data, key)
18
- result_data.delete(key)
17
+ def finalize_graphql_result(query, result_data, key)
18
+ case result_data
19
+ when Hash
20
+ result_data.delete(key)
21
+ when Array
22
+ result_data.delete_at(key)
23
+ else
24
+ raise "Unexpected result data #{result_data.class}: #{result_data}"
25
+ end
19
26
  end
20
27
  end
21
28
  end
@@ -4,17 +4,6 @@ module GraphQL
4
4
  # the error will be inserted into the response's `"errors"` key
5
5
  # and the field will resolve to `nil`.
6
6
  class ExecutionError < GraphQL::RuntimeError
7
- # @return [GraphQL::Language::Nodes::Field] the field where the error occurred
8
- def ast_node
9
- ast_nodes&.first
10
- end
11
-
12
- def ast_node=(new_node)
13
- @ast_nodes = [new_node]
14
- end
15
-
16
- attr_accessor :ast_nodes
17
-
18
7
  # @return [String] an array describing the JSON-path into the execution
19
8
  # response which corresponds to this error.
20
9
  attr_accessor :path
@@ -36,8 +25,13 @@ module GraphQL
36
25
  super(message)
37
26
  end
38
27
 
39
- def assign_graphql_result(query, result_data, key)
40
- result_data[key] = nil
28
+ def finalize_graphql_result(query, result_data, key)
29
+ if ast_node.is_a?(GraphQL::Language::Nodes::Directive)
30
+ # This is for backwards compatibility ... what does the spec say?
31
+ result_data.delete(key)
32
+ else
33
+ result_data[key] = nil
34
+ end
41
35
  end
42
36
 
43
37
  # @return [Hash] An entry for the response's "errors" key
@@ -2,8 +2,8 @@
2
2
  module GraphQL
3
3
  module Introspection
4
4
  class EntryPoints < Introspection::BaseObject
5
- field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false, dynamic_introspection: true, resolve_static: :__schema
6
- field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system", dynamic_introspection: true, resolve_static: :__type do
5
+ field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false, dynamic_introspection: true, resolve_static: true
6
+ field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system", dynamic_introspection: true, resolve_static: true do
7
7
  argument :name, String
8
8
  end
9
9
 
@@ -13,12 +13,16 @@ module GraphQL
13
13
  field :mutation_type, GraphQL::Schema::LateBoundType.new("__Type"), "If this server supports mutation, the type that mutation operations will be rooted at."
14
14
  field :subscription_type, GraphQL::Schema::LateBoundType.new("__Type"), "If this server support subscription, the type that subscription operations will be rooted at."
15
15
  field :directives, [GraphQL::Schema::LateBoundType.new("__Directive")], "A list of all directives supported by this server.", null: false, scope: false
16
- field :description, String, resolver_method: :schema_description
16
+ field :description, String, resolver_method: :schema_description, resolve_static: :schema_description
17
17
 
18
- def schema_description
18
+ def self.schema_description(context)
19
19
  context.schema.description
20
20
  end
21
21
 
22
+ def schema_description
23
+ self.class.schema_description(context)
24
+ end
25
+
22
26
  def types
23
27
  query_types = context.types.all_types
24
28
  types = query_types + context.schema.extra_types
@@ -28,7 +28,15 @@ module GraphQL
28
28
  attr_reader :pos, :tokens_count
29
29
 
30
30
  def advance
31
- @scanner.skip(IGNORE_REGEXP)
31
+ loop do
32
+ @scanner.skip(IGNORE_REGEXP)
33
+ if @scanner.skip(COMMENT_REGEXP)
34
+ @tokens_count += 1
35
+ next
36
+ end
37
+ break
38
+ end
39
+
32
40
  if @scanner.eos?
33
41
  @finished = true
34
42
  return false
@@ -122,7 +130,7 @@ module GraphQL
122
130
  elsif token_name == :STRING
123
131
  string_value
124
132
  elsif @scanner.matched_size.nil?
125
- @scanner.peek(1)
133
+ @string.byteslice(@scanner.pos - 1, 1)
126
134
  else
127
135
  token_value
128
136
  end
@@ -178,12 +186,8 @@ module GraphQL
178
186
  raise GraphQL::ParseError.new(message, line, col, @string, filename: @filename)
179
187
  end
180
188
 
181
- IGNORE_REGEXP = %r{
182
- (?:
183
- [, \c\r\n\t]+ |
184
- \#.*$
185
- )*
186
- }x
189
+ IGNORE_REGEXP = /[, \c\r\n\t]+/
190
+ COMMENT_REGEXP = /\#[^\n]*/
187
191
  IDENTIFIER_REGEXP = /[_A-Za-z][_0-9A-Za-z]*/
188
192
  INT_REGEXP = /-?(?:[0]|[1-9][0-9]*)/
189
193
  FLOAT_DECIMAL_REGEXP = /[.][0-9]+/
@@ -806,7 +806,7 @@ module GraphQL
806
806
 
807
807
  def expect_token(expected_token_name)
808
808
  unless @token_name == expected_token_name
809
- raise_parse_error("Expected #{expected_token_name}, actual: #{token_name || "(none)"} (#{debug_token_value.inspect})")
809
+ raise_parse_error("Expected #{expected_token_name}, #{@token_name == false ? "not end of file" : "actual: #{@token_name} (#{debug_token_value.inspect})"}")
810
810
  end
811
811
  advance_token
812
812
  end