graphql 2.5.20 → 2.6.0

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