graphql 2.5.9 → 2.5.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  4. data/lib/graphql/analysis.rb +20 -13
  5. data/lib/graphql/dashboard/application_controller.rb +41 -0
  6. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  7. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  8. data/lib/graphql/dashboard/subscriptions.rb +2 -1
  9. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
  10. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
  11. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
  12. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
  13. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
  14. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
  15. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
  16. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
  17. data/lib/graphql/dashboard.rb +11 -73
  18. data/lib/graphql/dataloader/active_record_association_source.rb +14 -2
  19. data/lib/graphql/dataloader/async_dataloader.rb +22 -11
  20. data/lib/graphql/dataloader/null_dataloader.rb +54 -9
  21. data/lib/graphql/dataloader.rb +75 -23
  22. data/lib/graphql/date_encoding_error.rb +1 -1
  23. data/lib/graphql/execution/field_resolve_step.rb +631 -0
  24. data/lib/graphql/execution/finalize.rb +217 -0
  25. data/lib/graphql/execution/input_values.rb +261 -0
  26. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  27. data/lib/graphql/execution/interpreter/resolve.rb +10 -16
  28. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
  29. data/lib/graphql/execution/interpreter/runtime.rb +28 -33
  30. data/lib/graphql/execution/interpreter.rb +8 -22
  31. data/lib/graphql/execution/lazy.rb +1 -1
  32. data/lib/graphql/execution/load_argument_step.rb +64 -0
  33. data/lib/graphql/execution/multiplex.rb +1 -1
  34. data/lib/graphql/execution/next.rb +90 -0
  35. data/lib/graphql/execution/prepare_object_step.rb +128 -0
  36. data/lib/graphql/execution/runner.rb +410 -0
  37. data/lib/graphql/execution/selections_step.rb +91 -0
  38. data/lib/graphql/execution.rb +8 -4
  39. data/lib/graphql/execution_error.rb +17 -10
  40. data/lib/graphql/introspection/directive_type.rb +7 -3
  41. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  42. data/lib/graphql/introspection/entry_points.rb +11 -3
  43. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  44. data/lib/graphql/introspection/field_type.rb +13 -5
  45. data/lib/graphql/introspection/input_value_type.rb +21 -13
  46. data/lib/graphql/introspection/type_type.rb +64 -28
  47. data/lib/graphql/invalid_null_error.rb +11 -5
  48. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  49. data/lib/graphql/language/lexer.rb +20 -9
  50. data/lib/graphql/language/nodes.rb +5 -1
  51. data/lib/graphql/language/parser.rb +1 -0
  52. data/lib/graphql/language.rb +21 -12
  53. data/lib/graphql/pagination/connection.rb +2 -0
  54. data/lib/graphql/pagination/connections.rb +32 -0
  55. data/lib/graphql/query/context.rb +11 -4
  56. data/lib/graphql/query/null_context.rb +9 -3
  57. data/lib/graphql/query/partial.rb +18 -3
  58. data/lib/graphql/query.rb +10 -1
  59. data/lib/graphql/runtime_error.rb +6 -0
  60. data/lib/graphql/schema/addition.rb +3 -1
  61. data/lib/graphql/schema/argument.rb +17 -0
  62. data/lib/graphql/schema/build_from_definition.rb +15 -2
  63. data/lib/graphql/schema/directive.rb +45 -13
  64. data/lib/graphql/schema/field/connection_extension.rb +4 -37
  65. data/lib/graphql/schema/field/scope_extension.rb +18 -13
  66. data/lib/graphql/schema/field.rb +87 -48
  67. data/lib/graphql/schema/field_extension.rb +11 -8
  68. data/lib/graphql/schema/interface.rb +26 -0
  69. data/lib/graphql/schema/list.rb +5 -1
  70. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -11
  71. data/lib/graphql/schema/member/has_arguments.rb +43 -14
  72. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  73. data/lib/graphql/schema/member/has_dataloader.rb +37 -0
  74. data/lib/graphql/schema/member/has_fields.rb +86 -5
  75. data/lib/graphql/schema/member/has_interfaces.rb +2 -2
  76. data/lib/graphql/schema/member/type_system_helpers.rb +16 -2
  77. data/lib/graphql/schema/member.rb +5 -0
  78. data/lib/graphql/schema/non_null.rb +1 -1
  79. data/lib/graphql/schema/object.rb +1 -0
  80. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  81. data/lib/graphql/schema/resolver.rb +60 -1
  82. data/lib/graphql/schema/subscription.rb +0 -2
  83. data/lib/graphql/schema/validator/required_validator.rb +45 -5
  84. data/lib/graphql/schema/visibility/migration.rb +2 -2
  85. data/lib/graphql/schema/visibility/profile.rb +140 -56
  86. data/lib/graphql/schema/visibility.rb +31 -18
  87. data/lib/graphql/schema/wrapper.rb +7 -1
  88. data/lib/graphql/schema.rb +108 -32
  89. data/lib/graphql/static_validation/all_rules.rb +1 -1
  90. data/lib/graphql/static_validation/base_visitor.rb +90 -66
  91. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  92. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  93. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
  94. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
  95. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
  96. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -4
  97. data/lib/graphql/static_validation/rules/fields_will_merge.rb +322 -256
  98. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
  99. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  100. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
  101. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
  102. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
  103. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
  104. data/lib/graphql/static_validation/validation_context.rb +1 -1
  105. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  106. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +34 -10
  107. data/lib/graphql/subscriptions/event.rb +1 -0
  108. data/lib/graphql/subscriptions.rb +36 -1
  109. data/lib/graphql/testing/helpers.rb +12 -9
  110. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  111. data/lib/graphql/testing.rb +1 -0
  112. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  113. data/lib/graphql/tracing/detailed_trace.rb +70 -7
  114. data/lib/graphql/tracing/null_trace.rb +1 -1
  115. data/lib/graphql/tracing/perfetto_trace.rb +209 -79
  116. data/lib/graphql/tracing/sentry_trace.rb +3 -1
  117. data/lib/graphql/tracing/trace.rb +6 -0
  118. data/lib/graphql/type_kinds.rb +1 -0
  119. data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
  120. data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
  121. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  122. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  123. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  124. data/lib/graphql/unauthorized_error.rb +9 -1
  125. data/lib/graphql/version.rb +1 -1
  126. data/lib/graphql.rb +7 -3
  127. metadata +21 -3
@@ -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,5 +1,6 @@
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"
@@ -9,10 +10,13 @@ require "graphql/execution/errors"
9
10
  module GraphQL
10
11
  module Execution
11
12
  # @api private
12
- class Skip < GraphQL::Error; end
13
+ class Skip < GraphQL::RuntimeError
14
+ attr_accessor :path
15
+ def ast_nodes=(_ignored); end
13
16
 
14
- # Just a singleton for implementing {Query::Context#skip}
15
- # @api private
16
- SKIP = Skip.new
17
+ def finalize_graphql_result(query, result_data, key)
18
+ result_data.delete(key)
19
+ end
20
+ end
17
21
  end
18
22
  end
@@ -3,9 +3,17 @@ module GraphQL
3
3
  # If a field's resolve function returns a {ExecutionError},
4
4
  # the error will be inserted into the response's `"errors"` key
5
5
  # and the field will resolve to `nil`.
6
- class ExecutionError < GraphQL::Error
6
+ class ExecutionError < GraphQL::RuntimeError
7
7
  # @return [GraphQL::Language::Nodes::Field] the field where the error occurred
8
- attr_accessor :ast_node
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
9
17
 
10
18
  # @return [String] an array describing the JSON-path into the execution
11
19
  # response which corresponds to this error.
@@ -21,25 +29,24 @@ module GraphQL
21
29
  # under the `extensions` key.
22
30
  attr_accessor :extensions
23
31
 
24
- def initialize(message, ast_node: nil, options: nil, extensions: nil)
25
- @ast_node = ast_node
32
+ def initialize(message, ast_node: nil, ast_nodes: nil, options: nil, extensions: nil)
33
+ @ast_nodes = ast_nodes || [ast_node]
26
34
  @options = options
27
35
  @extensions = extensions
28
36
  super(message)
29
37
  end
30
38
 
39
+ def finalize_graphql_result(query, result_data, key)
40
+ result_data[key] = nil
41
+ end
42
+
31
43
  # @return [Hash] An entry for the response's "errors" key
32
44
  def to_h
33
45
  hash = {
34
46
  "message" => message,
35
47
  }
36
48
  if ast_node
37
- hash["locations"] = [
38
- {
39
- "line" => ast_node.line,
40
- "column" => ast_node.col,
41
- }
42
- ]
49
+ hash["locations"] = @ast_nodes.map { |a| { "line" => a.line, "column" => a.col } }
43
50
  end
44
51
  if path
45
52
  hash["path"] = path
@@ -12,7 +12,7 @@ module GraphQL
12
12
  field :name, String, null: false, method: :graphql_name
13
13
  field :description, String
14
14
  field :locations, [GraphQL::Schema::LateBoundType.new("__DirectiveLocation")], null: false, scope: false
15
- field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false do
15
+ field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false, resolve_each: :resolve_args do
16
16
  argument :include_deprecated, Boolean, required: false, default_value: false
17
17
  end
18
18
  field :on_operation, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_operation?
@@ -21,11 +21,15 @@ module GraphQL
21
21
 
22
22
  field :is_repeatable, Boolean, method: :repeatable?
23
23
 
24
- def args(include_deprecated:)
25
- args = @context.types.arguments(@object)
24
+ def self.resolve_args(object, context, include_deprecated:)
25
+ args = context.types.arguments(object)
26
26
  args = args.reject(&:deprecation_reason) unless include_deprecated
27
27
  args
28
28
  end
29
+
30
+ def args(include_deprecated:)
31
+ self.class.resolve_args(object, context, include_deprecated: include_deprecated)
32
+ end
29
33
  end
30
34
  end
31
35
  end
@@ -2,9 +2,13 @@
2
2
  module GraphQL
3
3
  module Introspection
4
4
  class DynamicFields < Introspection::BaseObject
5
- field :__typename, String, "The name of this type", null: false, dynamic_introspection: true
5
+ field :__typename, String, "The name of this type", null: false, dynamic_introspection: true, resolve_each: true
6
6
 
7
7
  def __typename
8
+ self.class.__typename(object, context)
9
+ end
10
+
11
+ def self.__typename(object, context)
8
12
  object.class.graphql_name
9
13
  end
10
14
  end
@@ -2,19 +2,27 @@
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
6
- field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system", dynamic_introspection: true do
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
7
7
  argument :name, String
8
8
  end
9
9
 
10
- def __schema
10
+ def self.__schema(context)
11
11
  # Apply wrapping manually since this field isn't wrapped by instrumentation
12
12
  schema = context.schema
13
13
  schema_type = schema.introspection_system.types["__Schema"]
14
14
  schema_type.wrap(schema, context)
15
15
  end
16
16
 
17
+ def __schema
18
+ self.class.__schema(context)
19
+ end
20
+
17
21
  def __type(name:)
22
+ self.class.__type(context, name: name)
23
+ end
24
+
25
+ def self.__type(context, name:)
18
26
  if context.types.reachable_type?(name) && (type = context.types.type(name))
19
27
  type
20
28
  elsif (type = context.schema.extra_types.find { |t| t.graphql_name == name })
@@ -6,17 +6,17 @@ module GraphQL
6
6
  description "One possible value for a given Enum. Enum values are unique values, not a "\
7
7
  "placeholder for a string or numeric value. However an Enum value is returned in "\
8
8
  "a JSON response as a string."
9
- field :name, String, null: false
9
+ field :name, String, null: false, method: :graphql_name
10
10
  field :description, String
11
- field :is_deprecated, Boolean, null: false
11
+ field :is_deprecated, Boolean, null: false, resolve_each: :resolve_is_deprecated
12
12
  field :deprecation_reason, String
13
13
 
14
- def name
15
- object.graphql_name
14
+ def self.resolve_is_deprecated(object, context)
15
+ !!object.deprecation_reason
16
16
  end
17
17
 
18
18
  def is_deprecated
19
- !!@object.deprecation_reason
19
+ self.class.resolve_is_deprecated(object, context)
20
20
  end
21
21
  end
22
22
  end