graphql 2.5.20 → 2.5.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +3 -3
  3. data/lib/graphql/execution/multiplex.rb +1 -1
  4. data/lib/graphql/execution/next/field_resolve_step.rb +690 -0
  5. data/lib/graphql/execution/next/load_argument_step.rb +60 -0
  6. data/lib/graphql/execution/{batching → next}/prepare_object_step.rb +26 -9
  7. data/lib/graphql/execution/{batching → next}/runner.rb +65 -28
  8. data/lib/graphql/execution/{batching → next}/selections_step.rb +1 -1
  9. data/lib/graphql/execution/{batching.rb → next.rb} +19 -12
  10. data/lib/graphql/execution.rb +1 -0
  11. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  12. data/lib/graphql/introspection/entry_points.rb +5 -1
  13. data/lib/graphql/pagination/connection.rb +2 -0
  14. data/lib/graphql/pagination/connections.rb +32 -0
  15. data/lib/graphql/schema/argument.rb +1 -0
  16. data/lib/graphql/schema/build_from_definition.rb +12 -25
  17. data/lib/graphql/schema/field/connection_extension.rb +15 -35
  18. data/lib/graphql/schema/field/scope_extension.rb +22 -13
  19. data/lib/graphql/schema/field.rb +52 -58
  20. data/lib/graphql/schema/field_extension.rb +33 -0
  21. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
  22. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  23. data/lib/graphql/schema/member/has_dataloader.rb +8 -0
  24. data/lib/graphql/schema/member/has_fields.rb +5 -4
  25. data/lib/graphql/schema/member.rb +5 -0
  26. data/lib/graphql/schema/object.rb +1 -0
  27. data/lib/graphql/schema/resolver.rb +42 -0
  28. data/lib/graphql/types/relay/has_node_field.rb +10 -2
  29. data/lib/graphql/types/relay/has_nodes_field.rb +10 -2
  30. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  31. data/lib/graphql/version.rb +1 -1
  32. metadata +9 -8
  33. data/lib/graphql/execution/batching/field_compatibility.rb +0 -150
  34. data/lib/graphql/execution/batching/field_resolve_step.rb +0 -408
@@ -1,150 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Execution
4
- module Batching
5
- module FieldCompatibility
6
- def resolve_all_load_arguments(frs, object_from_id_receiver, arguments, argument_owner, context)
7
- arg_defns = context.types.arguments(argument_owner)
8
- arg_defns.each do |arg_defn|
9
- if arg_defn.loads
10
- if arguments.key?(arg_defn.keyword)
11
- id = arguments.delete(arg_defn.keyword)
12
- if !id.nil?
13
- value = if arg_defn.type.list?
14
- id.map { |inner_id|
15
- object_from_id_receiver.load_and_authorize_application_object(arg_defn, inner_id, context)
16
- }
17
- else
18
- object_from_id_receiver.load_and_authorize_application_object(arg_defn, id, context)
19
- end
20
-
21
- if frs.runner.resolves_lazies
22
- value = frs.sync(value)
23
- end
24
- if value.is_a?(GraphQL::Error)
25
- value.path = frs.path
26
- return value
27
- end
28
- else
29
- value = nil
30
- end
31
- arguments[arg_defn.keyword] = value
32
- end
33
- elsif (input_type = arg_defn.type.unwrap).kind.input_object? &&
34
- (value = arguments[arg_defn.keyword]) # TODO lists
35
- resolve_all_load_arguments(frs, object_from_id_receiver, value, input_type, context)
36
- end
37
- end
38
- nil
39
- end
40
-
41
- def resolve_batch(frs, objects, context, kwargs)
42
- if @batch_mode && !:direct_send.equal?(@batch_mode)
43
- return super
44
- end
45
-
46
- if !@resolver_class
47
- maybe_err = resolve_all_load_arguments(frs, self, kwargs, self, context)
48
- if maybe_err
49
- return maybe_err
50
- end
51
- end
52
- if extras.include?(:lookahead)
53
- if kwargs.frozen?
54
- kwargs = kwargs.dup
55
- end
56
- kwargs[:lookahead] = Execution::Lookahead.new(
57
- query: context.query,
58
- ast_nodes: frs.ast_nodes || Array(frs.ast_node),
59
- field: self,
60
- )
61
- end
62
-
63
- if extras.include?(:ast_node)
64
- if kwargs.frozen?
65
- kwargs = kwargs.dup
66
- end
67
- kwargs[:ast_node] = frs.ast_node
68
- end
69
-
70
- if @owner.method_defined?(@resolver_method)
71
- results = []
72
- frs.selections_step.graphql_objects.each_with_index do |obj_inst, idx|
73
- if frs.object_is_authorized[idx]
74
- if dynamic_introspection
75
- obj_inst = @owner.wrap(obj_inst, context)
76
- end
77
- results << with_extensions(obj_inst, kwargs, context) do |obj, ruby_kwargs|
78
- if ruby_kwargs.empty?
79
- obj.public_send(@resolver_method)
80
- else
81
- obj.public_send(@resolver_method, **ruby_kwargs)
82
- end
83
- end
84
- end
85
- end
86
- results
87
- elsif @resolver_class
88
- objects.map do |o|
89
- resolver_inst_kwargs = kwargs.dup
90
- resolver_inst = @resolver_class.new(object: o, context: context, field: self)
91
- maybe_err = resolve_all_load_arguments(frs, resolver_inst, resolver_inst_kwargs, self, context)
92
- if maybe_err
93
- next maybe_err
94
- end
95
- resolver_inst_kwargs = if @resolver_class < Schema::HasSingleInputArgument
96
- resolver_inst_kwargs[:input]
97
- else
98
- resolver_inst_kwargs
99
- end
100
- with_extensions(o, resolver_inst_kwargs, context) do |obj, ruby_kwargs|
101
- resolver_inst.object = obj
102
- resolver_inst.prepared_arguments = ruby_kwargs
103
- is_authed, new_return_value = resolver_inst.authorized?(**ruby_kwargs)
104
- if frs.runner.resolves_lazies && frs.runner.schema.lazy?(is_authed)
105
- is_authed, new_return_value = frs.runner.schema.sync_lazy(is_authed)
106
- end
107
- if is_authed
108
- resolver_inst.call_resolve(ruby_kwargs)
109
- else
110
- new_return_value
111
- end
112
- end
113
- rescue RuntimeError => err
114
- err
115
- rescue StandardError => stderr
116
- begin
117
- context.query.handle_or_reraise(stderr)
118
- rescue GraphQL::ExecutionError => ex_err
119
- ex_err
120
- end
121
- end
122
- elsif objects.first.is_a?(Hash)
123
- objects.map { |o| o[method_sym] || o[graphql_name] }
124
- elsif objects.first.is_a?(Interpreter::RawValue)
125
- objects
126
- else
127
- # need to use connection extension if present, and extensions expect object type instances
128
- if extensions.empty?
129
- objects.map { |o| o.public_send(@method_sym)}
130
- else
131
- results = []
132
- frs.selections_step.graphql_objects.each_with_index do |obj_inst, idx|
133
- if frs.object_is_authorized[idx]
134
- results << with_extensions(obj_inst, EmptyObjects::EMPTY_HASH, context) do |obj, arguments|
135
- if arguments.empty?
136
- obj.object.public_send(@method_sym)
137
- else
138
- obj.object.public_send(@method_sym, **arguments)
139
- end
140
- end
141
- end
142
- end
143
- results
144
- end
145
- end
146
- end
147
- end
148
- end
149
- end
150
- end
@@ -1,408 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Execution
4
- module Batching
5
- class FieldResolveStep
6
- def initialize(parent_type:, runner:, key:, selections_step:)
7
- @selections_step = selections_step
8
- @key = key
9
- @parent_type = parent_type
10
- @ast_node = @ast_nodes = nil
11
- @runner = runner
12
- @field_definition = nil
13
- @arguments = nil
14
- @field_results = nil
15
- @path = nil
16
- @enqueued_authorization = false
17
- @pending_authorize_steps_count = 0
18
- @all_next_objects = nil
19
- @all_next_results = nil
20
- @static_type = nil
21
- @next_selections = nil
22
- @object_is_authorized = nil
23
- end
24
-
25
- attr_reader :ast_node, :key, :parent_type, :selections_step, :runner, :field_definition, :object_is_authorized, :arguments
26
-
27
- def path
28
- @path ||= [*@selections_step.path, @key].freeze
29
- end
30
-
31
- def ast_nodes
32
- @ast_nodes ||= [@ast_node]
33
- end
34
-
35
- def append_selection(ast_node)
36
- if @ast_node.nil?
37
- @ast_node = ast_node
38
- elsif @ast_nodes.nil?
39
- @ast_nodes = [@ast_node, ast_node]
40
- else
41
- @ast_nodes << ast_node
42
- end
43
- nil
44
- end
45
-
46
- def coerce_arguments(argument_owner, ast_arguments_or_hash)
47
- arg_defns = argument_owner.arguments(@selections_step.query.context)
48
- if arg_defns.empty?
49
- return EmptyObjects::EMPTY_HASH
50
- end
51
- args_hash = {}
52
- if ast_arguments_or_hash.is_a?(Hash)
53
- ast_arguments_or_hash.each do |key, value|
54
- key_s = nil
55
- arg_defn = arg_defns.each_value.find { |a|
56
- a.keyword == key || a.graphql_name == (key_s ||= String(key))
57
- }
58
- arg_value = coerce_argument_value(arg_defn.type, value)
59
- args_hash[arg_defn.keyword] = arg_value
60
- end
61
- else
62
- ast_arguments_or_hash.each { |arg_node|
63
- arg_defn = arg_defns[arg_node.name]
64
- arg_value = coerce_argument_value(arg_defn.type, arg_node.value)
65
- arg_key = arg_defn.keyword
66
- args_hash[arg_key] = arg_value
67
- }
68
- end
69
-
70
- arg_defns.each do |arg_graphql_name, arg_defn|
71
- if arg_defn.default_value? && !args_hash.key?(arg_defn.keyword)
72
- args_hash[arg_defn.keyword] = arg_defn.default_value
73
- end
74
- end
75
-
76
- args_hash
77
- end
78
-
79
- def coerce_argument_value(arg_t, arg_value)
80
- if arg_t.non_null?
81
- arg_t = arg_t.of_type
82
- end
83
-
84
- if arg_value.is_a?(Language::Nodes::VariableIdentifier)
85
- vars = @selections_step.query.variables
86
- arg_value = if vars.key?(arg_value.name)
87
- vars[arg_value.name]
88
- elsif vars.key?(arg_value.name.to_sym)
89
- vars[arg_value.name.to_sym]
90
- end
91
- elsif arg_value.is_a?(Language::Nodes::NullValue)
92
- arg_value = nil
93
- elsif arg_value.is_a?(Language::Nodes::Enum)
94
- arg_value = arg_value.name
95
- elsif arg_value.is_a?(Language::Nodes::InputObject)
96
- arg_value = arg_value.arguments # rubocop:disable Development/ContextIsPassedCop
97
- end
98
-
99
- if arg_t.list?
100
- if arg_value.nil?
101
- arg_value
102
- else
103
- arg_value = Array(arg_value)
104
- inner_t = arg_t.of_type
105
- arg_value.map { |v| coerce_argument_value(inner_t, v) }
106
- end
107
- elsif arg_t.kind.leaf?
108
- begin
109
- ctx = @selections_step.query.context
110
- arg_t.coerce_input(arg_value, ctx)
111
- rescue GraphQL::UnauthorizedEnumValueError => enum_err
112
- begin
113
- @runner.schema.unauthorized_object(enum_err)
114
- rescue GraphQL::ExecutionError => ex_err
115
- ex_err
116
- end
117
- end
118
- elsif arg_t.kind.input_object?
119
- coerce_arguments(arg_t, arg_value)
120
- else
121
- raise "Unsupported argument value: #{arg_t.to_type_signature} / #{arg_value.class} (#{arg_value.inspect})"
122
- end
123
- end
124
-
125
- # Implement that Lazy API
126
- def value
127
- @field_results = sync(@field_results)
128
- @runner.add_step(self)
129
- true
130
- end
131
-
132
- def sync(lazy)
133
- if lazy.is_a?(Array)
134
- lazy.map! { |l| sync(l)}
135
- else
136
- @runner.schema.sync_lazy(lazy)
137
- end
138
- rescue GraphQL::UnauthorizedError => auth_err
139
- @runner.schema.unauthorized_object(auth_err)
140
- rescue GraphQL::ExecutionError => err
141
- err
142
- end
143
-
144
- def call
145
- if @enqueued_authorization && @pending_authorize_steps_count == 0
146
- enqueue_next_steps
147
- elsif @field_results
148
- build_results
149
- else
150
- execute_field
151
- end
152
- end
153
-
154
- def add_graphql_error(err)
155
- err.path = path
156
- err.ast_nodes = ast_nodes
157
- @selections_step.query.context.add_error(err)
158
- err
159
- end
160
-
161
- module AlwaysAuthorized
162
- def self.[](_key)
163
- true
164
- end
165
- end
166
-
167
- def execute_field
168
- field_name = @ast_node.name
169
- @field_definition = @selections_step.query.get_field(@parent_type, field_name) || raise("Invariant: no field found for #{@parent_type.to_type_signature}.#{ast_node.name}")
170
- objects = @selections_step.objects
171
- if field_name == "__typename"
172
- # TODO handle custom introspection
173
- @field_results = Array.new(objects.size, @parent_type.graphql_name)
174
- @object_is_authorized = AlwaysAuthorized
175
- build_results
176
- return
177
- end
178
-
179
- @arguments = coerce_arguments(@field_definition, @ast_node.arguments) # rubocop:disable Development/ContextIsPassedCop
180
-
181
-
182
- ctx = @selections_step.query.context
183
-
184
- if (@runner.authorizes.fetch(@field_definition) { @runner.authorizes[@field_definition] = @field_definition.authorizes?(ctx) })
185
- authorized_objects = []
186
- @object_is_authorized = objects.map { |o|
187
- is_authed = @field_definition.authorized?(o, @arguments, ctx)
188
- if is_authed
189
- authorized_objects << o
190
- end
191
- is_authed
192
- }
193
- else
194
- authorized_objects = objects
195
- @object_is_authorized = AlwaysAuthorized
196
- end
197
-
198
- @field_results = @field_definition.resolve_batch(self, authorized_objects, ctx, @arguments)
199
-
200
- if @runner.resolves_lazies # TODO extract this
201
- lazies = false
202
- @field_results.each do |field_result|
203
- if @runner.schema.lazy?(field_result)
204
- lazies = true
205
- break
206
- elsif field_result.is_a?(Array)
207
- field_result.each do |inner_fr|
208
- if @runner.schema.lazy?(inner_fr)
209
- break lazies = true
210
- end
211
- end
212
- if lazies
213
- break
214
- end
215
- end
216
- end
217
-
218
- if lazies
219
- @runner.dataloader.lazy_at_depth(path.size, self)
220
- else
221
- build_results
222
- end
223
- else
224
- build_results
225
- end
226
- end
227
-
228
- def build_results
229
- return_type = @field_definition.type
230
- return_result_type = return_type.unwrap
231
-
232
- if return_result_type.kind.composite?
233
- @static_type = return_result_type
234
- if @ast_nodes
235
- @next_selections = []
236
- @ast_nodes.each do |ast_node|
237
- @next_selections.concat(ast_node.selections)
238
- end
239
- else
240
- @next_selections = @ast_node.selections
241
- end
242
-
243
- @all_next_objects = []
244
- @all_next_results = []
245
-
246
- is_list = return_type.list?
247
- is_non_null = return_type.non_null?
248
- results = @selections_step.results
249
- field_result_idx = 0
250
- results.each_with_index do |result_h, i|
251
- if @object_is_authorized[i]
252
- result = @field_results[field_result_idx]
253
- field_result_idx += 1
254
- else
255
- result = nil
256
- end
257
- build_graphql_result(result_h, @key, result, return_type, is_non_null, is_list, false)
258
- end
259
- @enqueued_authorization = true
260
-
261
- if @pending_authorize_steps_count == 0
262
- enqueue_next_steps
263
- else
264
- # Do nothing -- it will enqueue itself later
265
- end
266
- else
267
- results = @selections_step.results
268
- ctx = @selections_step.query.context
269
- field_result_idx = 0
270
- results.each_with_index do |result_h, i|
271
- if @object_is_authorized[i]
272
- field_result = @field_results[field_result_idx]
273
- field_result_idx += 1
274
- else
275
- field_result = nil
276
- end
277
- result_h[@key] = if field_result.nil?
278
- if return_type.non_null?
279
- add_non_null_error(false)
280
- else
281
- nil
282
- end
283
- elsif field_result.is_a?(GraphQL::Error)
284
- add_graphql_error(field_result)
285
- else
286
- # TODO `nil`s in [T!] types aren't handled
287
- return_type.coerce_result(field_result, ctx)
288
- end
289
- end
290
- end
291
- end
292
-
293
- def enqueue_next_steps
294
- if !@all_next_results.empty?
295
- @all_next_objects.compact!
296
-
297
- if @static_type.kind.abstract?
298
- next_objects_by_type = Hash.new { |h, obj_t| h[obj_t] = [] }.compare_by_identity
299
- next_results_by_type = Hash.new { |h, obj_t| h[obj_t] = [] }.compare_by_identity
300
-
301
- ctx = nil
302
- @all_next_objects.each_with_index do |next_object, i|
303
- result = @all_next_results[i]
304
- if (object_type = @runner.runtime_types_at_result[result])
305
- # OK
306
- else
307
- ctx ||= @selections_step.query.context
308
- object_type, _unused_new_value = @runner.schema.resolve_type(@static_type, next_object, ctx)
309
- end
310
- next_objects_by_type[object_type] << next_object
311
- next_results_by_type[object_type] << result
312
- end
313
-
314
- next_objects_by_type.each do |obj_type, next_objects|
315
- @runner.add_step(SelectionsStep.new(
316
- path: path,
317
- parent_type: obj_type,
318
- selections: @next_selections,
319
- objects: next_objects,
320
- results: next_results_by_type[obj_type],
321
- runner: @runner,
322
- query: @selections_step.query,
323
- ))
324
- end
325
- else
326
- @runner.add_step(SelectionsStep.new(
327
- path: path,
328
- parent_type: @static_type,
329
- selections: @next_selections,
330
- objects: @all_next_objects,
331
- results: @all_next_results,
332
- runner: @runner,
333
- query: @selections_step.query,
334
- ))
335
- end
336
- end
337
- end
338
-
339
- def authorized_finished
340
- remaining = @pending_authorize_steps_count -= 1
341
- if @enqueued_authorization && remaining == 0
342
- @runner.add_step(self)
343
- end
344
- end
345
-
346
- def add_non_null_error(is_from_array)
347
- err = InvalidNullError.new(@parent_type, @field_definition, ast_nodes, is_from_array: is_from_array, path: path)
348
- @runner.schema.type_error(err, @selections_step.query.context)
349
- end
350
-
351
- private
352
-
353
- def build_graphql_result(graphql_result, key, field_result, return_type, is_nn, is_list, is_from_array) # rubocop:disable Metrics/ParameterLists
354
- if field_result.nil?
355
- if is_nn
356
- graphql_result[key] = add_non_null_error(is_from_array)
357
- else
358
- graphql_result[key] = nil
359
- end
360
- elsif field_result.is_a?(GraphQL::Error)
361
- graphql_result[key] = add_graphql_error(field_result)
362
- elsif is_list
363
- if is_nn
364
- return_type = return_type.of_type
365
- end
366
- inner_type = return_type.of_type
367
- inner_type_nn = inner_type.non_null?
368
- inner_type_l = inner_type.list?
369
- list_result = graphql_result[key] = []
370
- field_result.each_with_index do |inner_f_r, i|
371
- build_graphql_result(list_result, i, inner_f_r, inner_type, inner_type_nn, inner_type_l, true)
372
- end
373
- elsif @runner.resolves_lazies # Handle possible lazy resolve_type response
374
- @pending_authorize_steps_count += 1
375
- @runner.add_step(Batching::PrepareObjectStep.new(
376
- static_type: @static_type,
377
- object: field_result,
378
- runner: @runner,
379
- field_resolve_step: self,
380
- graphql_result: graphql_result,
381
- next_objects: @all_next_objects,
382
- next_results: @all_next_results,
383
- is_non_null: is_nn,
384
- key: key,
385
- is_from_array: is_from_array,
386
- ))
387
- else
388
- next_result_h = {}
389
- @all_next_results << next_result_h
390
- @all_next_objects << field_result
391
- @runner.static_types_at_result[next_result_h] = @static_type
392
- graphql_result[key] = next_result_h
393
- end
394
- end
395
- end
396
-
397
- class RawValueFieldResolveStep < FieldResolveStep
398
- def build_graphql_result(graphql_result, key, field_result, return_type, is_nn, is_list, is_from_array) # rubocop:disable Metrics/ParameterLists
399
- if field_result.is_a?(Interpreter::RawValue)
400
- graphql_result[key] = field_result.resolve
401
- else
402
- super
403
- end
404
- end
405
- end
406
- end
407
- end
408
- end