graphql-stitching 1.1.1 → 1.2.1

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -0
  3. data/Gemfile +0 -3
  4. data/README.md +10 -32
  5. data/docs/README.md +1 -0
  6. data/docs/client.md +2 -2
  7. data/docs/composer.md +1 -1
  8. data/docs/executor.md +8 -15
  9. data/docs/http_executable.md +51 -0
  10. data/docs/planner.md +12 -14
  11. data/docs/request.md +2 -0
  12. data/docs/supergraph.md +2 -2
  13. data/examples/file_uploads/Gemfile +9 -0
  14. data/examples/file_uploads/Procfile +2 -0
  15. data/examples/file_uploads/README.md +37 -0
  16. data/examples/file_uploads/file.txt +1 -0
  17. data/examples/file_uploads/gateway.rb +37 -0
  18. data/examples/file_uploads/helpers.rb +62 -0
  19. data/examples/file_uploads/remote.rb +21 -0
  20. data/examples/merged_types/Gemfile +8 -0
  21. data/examples/merged_types/Procfile +3 -0
  22. data/examples/merged_types/README.md +33 -0
  23. data/{example → examples/merged_types}/gateway.rb +4 -5
  24. data/examples/merged_types/remote1.rb +22 -0
  25. data/examples/merged_types/remote2.rb +22 -0
  26. data/lib/graphql/stitching/client.rb +9 -19
  27. data/lib/graphql/stitching/composer/base_validator.rb +3 -3
  28. data/lib/graphql/stitching/composer/validate_boundaries.rb +3 -3
  29. data/lib/graphql/stitching/composer/validate_interfaces.rb +3 -4
  30. data/lib/graphql/stitching/composer.rb +66 -9
  31. data/lib/graphql/stitching/executor/boundary_source.rb +4 -6
  32. data/lib/graphql/stitching/executor/root_source.rb +4 -4
  33. data/lib/graphql/stitching/executor.rb +16 -13
  34. data/lib/graphql/stitching/export_selection.rb +6 -1
  35. data/lib/graphql/stitching/http_executable.rb +145 -4
  36. data/lib/graphql/stitching/planner.rb +3 -3
  37. data/lib/graphql/stitching/request.rb +66 -4
  38. data/lib/graphql/stitching/shaper.rb +4 -3
  39. data/lib/graphql/stitching/skip_include.rb +4 -3
  40. data/lib/graphql/stitching/supergraph/resolver_directive.rb +17 -0
  41. data/lib/graphql/stitching/supergraph/source_directive.rb +12 -0
  42. data/lib/graphql/stitching/supergraph.rb +27 -34
  43. data/lib/graphql/stitching/util.rb +0 -9
  44. data/lib/graphql/stitching/version.rb +1 -1
  45. metadata +20 -7
  46. data/Procfile +0 -3
  47. data/example/remote1.rb +0 -26
  48. data/example/remote2.rb +0 -26
  49. /data/{example → examples/merged_types}/graphiql.html +0 -0
@@ -28,6 +28,7 @@ module GraphQL
28
28
 
29
29
  def execute(query:, variables: nil, operation_name: nil, context: nil, validate: true)
30
30
  request = GraphQL::Stitching::Request.new(
31
+ @supergraph,
31
32
  query,
32
33
  operation_name: operation_name,
33
34
  variables: variables,
@@ -35,24 +36,13 @@ module GraphQL
35
36
  )
36
37
 
37
38
  if validate
38
- validation_errors = @supergraph.schema.validate(request.document)
39
+ validation_errors = request.validate
39
40
  return error_result(validation_errors) if validation_errors.any?
40
41
  end
41
42
 
42
43
  request.prepare!
43
-
44
- plan = fetch_plan(request) do
45
- GraphQL::Stitching::Planner.new(
46
- supergraph: @supergraph,
47
- request: request,
48
- ).perform
49
- end
50
-
51
- GraphQL::Stitching::Executor.new(
52
- supergraph: @supergraph,
53
- request: request,
54
- plan: plan,
55
- ).perform
44
+ load_plan(request)
45
+ request.execute
56
46
  rescue GraphQL::ParseError, GraphQL::ExecutionError => e
57
47
  error_result([e])
58
48
  rescue StandardError => e
@@ -77,13 +67,13 @@ module GraphQL
77
67
 
78
68
  private
79
69
 
80
- def fetch_plan(request)
81
- if @on_cache_read
82
- cached_plan = @on_cache_read.call(request)
83
- return GraphQL::Stitching::Plan.from_json(JSON.parse(cached_plan)) if cached_plan
70
+ def load_plan(request)
71
+ if @on_cache_read && plan_json = @on_cache_read.call(request)
72
+ plan = GraphQL::Stitching::Plan.from_json(JSON.parse(plan_json))
73
+ return request.plan(plan)
84
74
  end
85
75
 
86
- plan = yield
76
+ plan = request.plan
87
77
 
88
78
  if @on_cache_write
89
79
  @on_cache_write.call(request, JSON.generate(plan.as_json))
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module GraphQL
4
- module Stitching
5
- class Composer::BaseValidator
3
+ module GraphQL::Stitching
4
+ class Composer
5
+ class BaseValidator
6
6
  def perform(ctx, composer)
7
7
  raise "not implemented"
8
8
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module GraphQL
4
- module Stitching
5
- class Composer::ValidateBoundaries < Composer::BaseValidator
3
+ module GraphQL::Stitching
4
+ class Composer
5
+ class ValidateBoundaries < BaseValidator
6
6
 
7
7
  def perform(ctx, composer)
8
8
  ctx.schema.types.each do |type_name, type|
@@ -1,9 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module GraphQL
4
- module Stitching
5
- class Composer::ValidateInterfaces < Composer::BaseValidator
6
-
3
+ module GraphQL::Stitching
4
+ class Composer
5
+ class ValidateInterfaces < BaseValidator
7
6
  # For each composed interface, check the interface against each possible type
8
7
  # to assure that intersecting fields have compatible types, structures, and nullability.
9
8
  # Verifies compatibility of types that inherit interface contracts through merging.
@@ -1,26 +1,49 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "./composer/base_validator"
4
+ require_relative "./composer/validate_interfaces"
5
+ require_relative "./composer/validate_boundaries"
6
+
3
7
  module GraphQL
4
8
  module Stitching
5
9
  class Composer
6
10
  class ComposerError < StitchingError; end
7
11
  class ValidationError < ComposerError; end
8
- class ReferenceType < GraphQL::Schema::Object
9
- field(:f, String) do
10
- argument(:a, String)
12
+
13
+ # @api private
14
+ NO_DEFAULT_VALUE = begin
15
+ class T < GraphQL::Schema::Object
16
+ field(:f, String) do
17
+ argument(:a, String)
18
+ end
11
19
  end
20
+
21
+ T.get_field("f").get_argument("a").default_value
12
22
  end
13
23
 
14
- NO_DEFAULT_VALUE = ReferenceType.get_field("f").get_argument("a").default_value
24
+ # @api private
15
25
  BASIC_VALUE_MERGER = ->(values_by_location, _info) { values_by_location.values.find { !_1.nil? } }
26
+
27
+ # @api private
16
28
  BASIC_ROOT_FIELD_LOCATION_SELECTOR = ->(locations, _info) { locations.last }
17
29
 
30
+ # @api private
18
31
  VALIDATORS = [
19
32
  "ValidateInterfaces",
20
33
  "ValidateBoundaries",
21
34
  ].freeze
22
35
 
23
- attr_reader :query_name, :mutation_name, :candidate_types_by_name_and_location, :schema_directives
36
+ # @return [String] name of the Query type in the composed schema.
37
+ attr_reader :query_name
38
+
39
+ # @return [String] name of the Mutation type in the composed schema.
40
+ attr_reader :mutation_name
41
+
42
+ # @api private
43
+ attr_reader :candidate_types_by_name_and_location
44
+
45
+ # @api private
46
+ attr_reader :schema_directives
24
47
 
25
48
  def initialize(
26
49
  query_name: "Query",
@@ -148,6 +171,8 @@ module GraphQL
148
171
  supergraph
149
172
  end
150
173
 
174
+ # @!scope class
175
+ # @!visibility private
151
176
  def prepare_locations_input(locations_input)
152
177
  schemas = {}
153
178
  executables = {}
@@ -200,6 +225,8 @@ module GraphQL
200
225
  return schemas, executables
201
226
  end
202
227
 
228
+ # @!scope class
229
+ # @!visibility private
203
230
  def build_directive(directive_name, directives_by_location)
204
231
  builder = self
205
232
 
@@ -212,6 +239,8 @@ module GraphQL
212
239
  end
213
240
  end
214
241
 
242
+ # @!scope class
243
+ # @!visibility private
215
244
  def build_scalar_type(type_name, types_by_location)
216
245
  built_in_type = GraphQL::Schema::BUILT_IN_TYPES[type_name]
217
246
  return built_in_type if built_in_type
@@ -225,6 +254,8 @@ module GraphQL
225
254
  end
226
255
  end
227
256
 
257
+ # @!scope class
258
+ # @!visibility private
228
259
  def build_enum_type(type_name, types_by_location, enum_usage)
229
260
  builder = self
230
261
 
@@ -261,6 +292,8 @@ module GraphQL
261
292
  end
262
293
  end
263
294
 
295
+ # @!scope class
296
+ # @!visibility private
264
297
  def build_object_type(type_name, types_by_location)
265
298
  builder = self
266
299
 
@@ -278,6 +311,8 @@ module GraphQL
278
311
  end
279
312
  end
280
313
 
314
+ # @!scope class
315
+ # @!visibility private
281
316
  def build_interface_type(type_name, types_by_location)
282
317
  builder = self
283
318
 
@@ -296,6 +331,8 @@ module GraphQL
296
331
  end
297
332
  end
298
333
 
334
+ # @!scope class
335
+ # @!visibility private
299
336
  def build_union_type(type_name, types_by_location)
300
337
  builder = self
301
338
 
@@ -309,6 +346,8 @@ module GraphQL
309
346
  end
310
347
  end
311
348
 
349
+ # @!scope class
350
+ # @!visibility private
312
351
  def build_input_object_type(type_name, types_by_location)
313
352
  builder = self
314
353
 
@@ -320,10 +359,14 @@ module GraphQL
320
359
  end
321
360
  end
322
361
 
362
+ # @!scope class
363
+ # @!visibility private
323
364
  def build_type_binding(type_name)
324
365
  GraphQL::Schema::LateBoundType.new(@mapped_type_names.fetch(type_name, type_name))
325
366
  end
326
367
 
368
+ # @!scope class
369
+ # @!visibility private
327
370
  def build_merged_fields(type_name, types_by_location, owner)
328
371
  # "field_name" => "location" => field
329
372
  fields_by_name_location = types_by_location.each_with_object({}) do |(location, type_candidate), memo|
@@ -356,6 +399,8 @@ module GraphQL
356
399
  end
357
400
  end
358
401
 
402
+ # @!scope class
403
+ # @!visibility private
359
404
  def build_merged_arguments(type_name, members_by_location, owner, field_name: nil, directive_name: nil)
360
405
  # "argument_name" => "location" => argument
361
406
  args_by_name_location = members_by_location.each_with_object({}) do |(location, member_candidate), memo|
@@ -410,6 +455,8 @@ module GraphQL
410
455
  end
411
456
  end
412
457
 
458
+ # @!scope class
459
+ # @!visibility private
413
460
  def build_merged_directives(type_name, members_by_location, owner, field_name: nil, argument_name: nil, enum_value: nil)
414
461
  directives_by_name_location = members_by_location.each_with_object({}) do |(location, member_candidate), memo|
415
462
  member_candidate.directives.each do |directive|
@@ -451,6 +498,8 @@ module GraphQL
451
498
  end
452
499
  end
453
500
 
501
+ # @!scope class
502
+ # @!visibility private
454
503
  def merge_value_types(type_name, type_candidates, field_name: nil, argument_name: nil)
455
504
  path = [type_name, field_name, argument_name].tap(&:compact!).join(".")
456
505
  alt_structures = type_candidates.map { Util.flatten_type_structure(_1) }
@@ -482,6 +531,8 @@ module GraphQL
482
531
  type
483
532
  end
484
533
 
534
+ # @!scope class
535
+ # @!visibility private
485
536
  def merge_descriptions(type_name, members_by_location, field_name: nil, argument_name: nil, enum_value: nil)
486
537
  strings_by_location = members_by_location.each_with_object({}) { |(l, m), memo| memo[l] = m.description }
487
538
  @description_merger.call(strings_by_location, {
@@ -492,6 +543,8 @@ module GraphQL
492
543
  }.tap(&:compact!))
493
544
  end
494
545
 
546
+ # @!scope class
547
+ # @!visibility private
495
548
  def merge_deprecations(type_name, members_by_location, field_name: nil, argument_name: nil, enum_value: nil)
496
549
  strings_by_location = members_by_location.each_with_object({}) { |(l, m), memo| memo[l] = m.deprecation_reason }
497
550
  @deprecation_merger.call(strings_by_location, {
@@ -502,6 +555,8 @@ module GraphQL
502
555
  }.tap(&:compact!))
503
556
  end
504
557
 
558
+ # @!scope class
559
+ # @!visibility private
505
560
  def extract_boundaries(type_name, types_by_location)
506
561
  types_by_location.each do |location, type_candidate|
507
562
  type_candidate.fields.each do |field_name, field_candidate|
@@ -554,6 +609,8 @@ module GraphQL
554
609
  end
555
610
  end
556
611
 
612
+ # @!scope class
613
+ # @!visibility private
557
614
  def select_root_field_locations(schema)
558
615
  [schema.query, schema.mutation].tap(&:compact!).each do |root_type|
559
616
  root_type.fields.each do |root_field_name, root_field|
@@ -572,6 +629,8 @@ module GraphQL
572
629
  end
573
630
  end
574
631
 
632
+ # @!scope class
633
+ # @!visibility private
575
634
  def expand_abstract_boundaries(schema)
576
635
  @boundary_map.keys.each do |type_name|
577
636
  boundary_type = schema.types[type_name]
@@ -585,6 +644,8 @@ module GraphQL
585
644
  end
586
645
  end
587
646
 
647
+ # @!scope class
648
+ # @!visibility private
588
649
  def build_enum_usage_map(schemas)
589
650
  reads = []
590
651
  writes = []
@@ -636,7 +697,3 @@ module GraphQL
636
697
  end
637
698
  end
638
699
  end
639
-
640
- require_relative "./composer/base_validator"
641
- require_relative "./composer/validate_interfaces"
642
- require_relative "./composer/validate_boundaries"
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module GraphQL
4
- module Stitching
5
- class Executor::BoundarySource < GraphQL::Dataloader::Source
3
+ module GraphQL::Stitching
4
+ class Executor
5
+ class BoundarySource < GraphQL::Dataloader::Source
6
6
  def initialize(executor, location)
7
7
  @executor = executor
8
8
  @location = location
@@ -29,7 +29,7 @@ module GraphQL
29
29
  @executor.request.operation_directives,
30
30
  )
31
31
  variables = @executor.request.variables.slice(*variable_names)
32
- raw_result = @executor.supergraph.execute_at_location(@location, query_document, variables, @executor.request.context)
32
+ raw_result = @executor.request.supergraph.execute_at_location(@location, query_document, variables, @executor.request)
33
33
  @executor.query_count += 1
34
34
 
35
35
  merge_results!(origin_sets_by_operation, raw_result.dig("data"))
@@ -183,9 +183,7 @@ module GraphQL
183
183
  end
184
184
 
185
185
  elsif forward_path.any?
186
- current_path << index
187
186
  repath_errors!(pathed_errors_by_object_id, forward_path, current_path, scope)
188
- current_path.pop
189
187
 
190
188
  elsif scope.is_a?(Array)
191
189
  scope.each_with_index do |element, index|
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module GraphQL
4
- module Stitching
5
- class Executor::RootSource < GraphQL::Dataloader::Source
3
+ module GraphQL::Stitching
4
+ class Executor
5
+ class RootSource < GraphQL::Dataloader::Source
6
6
  def initialize(executor, location)
7
7
  @executor = executor
8
8
  @location = location
@@ -17,7 +17,7 @@ module GraphQL
17
17
  @executor.request.operation_directives,
18
18
  )
19
19
  query_variables = @executor.request.variables.slice(*op.variables.keys)
20
- result = @executor.supergraph.execute_at_location(op.location, query_document, query_variables, @executor.request.context)
20
+ result = @executor.request.supergraph.execute_at_location(op.location, query_document, query_variables, @executor.request)
21
21
  @executor.query_count += 1
22
22
 
23
23
  @executor.data.merge!(result["data"]) if result["data"]
@@ -1,17 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
+ require_relative "./executor/boundary_source"
5
+ require_relative "./executor/root_source"
4
6
 
5
7
  module GraphQL
6
8
  module Stitching
7
9
  class Executor
8
- attr_reader :supergraph, :request, :plan, :data, :errors
10
+ # @return [Request] the stitching request to execute.
11
+ attr_reader :request
12
+
13
+ # @return [Hash] an aggregate data payload to return.
14
+ attr_reader :data
15
+
16
+ # @return [Array<Hash>] aggregate GraphQL errors to return.
17
+ attr_reader :errors
18
+
19
+ # @return [Integer] tally of queries performed while executing.
9
20
  attr_accessor :query_count
10
21
 
11
- def initialize(supergraph:, request:, plan:, nonblocking: false)
12
- @supergraph = supergraph
22
+ def initialize(request, nonblocking: false)
13
23
  @request = request
14
- @plan = plan
15
24
  @data = {}
16
25
  @errors = []
17
26
  @query_count = 0
@@ -24,10 +33,7 @@ module GraphQL
24
33
  result = {}
25
34
 
26
35
  if @data && @data.length > 0
27
- result["data"] = raw ? @data : GraphQL::Stitching::Shaper.new(
28
- supergraph: @supergraph,
29
- request: @request,
30
- ).perform!(@data)
36
+ result["data"] = raw ? @data : GraphQL::Stitching::Shaper.new(@request).perform!(@data)
31
37
  end
32
38
 
33
39
  if @errors.length > 0
@@ -40,13 +46,13 @@ module GraphQL
40
46
  private
41
47
 
42
48
  def exec!(next_steps = [0])
43
- if @exec_cycles > @plan.ops.length
49
+ if @exec_cycles > @request.plan.ops.length
44
50
  # sanity check... if we've exceeded queue size, then something went wrong.
45
51
  raise StitchingError, "Too many execution requests attempted."
46
52
  end
47
53
 
48
54
  @dataloader.append_job do
49
- tasks = @plan
55
+ tasks = @request.plan
50
56
  .ops
51
57
  .select { next_steps.include?(_1.after) }
52
58
  .group_by { [_1.location, _1.boundary.nil?] }
@@ -69,6 +75,3 @@ module GraphQL
69
75
  end
70
76
  end
71
77
  end
72
-
73
- require_relative "./executor/boundary_source"
74
- require_relative "./executor/root_source"
@@ -20,8 +20,13 @@ module GraphQL
20
20
  "#{EXPORT_PREFIX}#{name}"
21
21
  end
22
22
 
23
+ # The argument assigning Field.alias changed from
24
+ # a generic `alias` hash key to a structured `field_alias` kwarg.
25
+ # See https://github.com/rmosolgo/graphql-ruby/pull/4718
26
+ FIELD_ALIAS_KWARG = !GraphQL::Language::Nodes::Field.new(field_alias: "a").alias.nil?
27
+
23
28
  def key_node(field_name)
24
- if Util.graphql_version?(2, 2)
29
+ if FIELD_ALIAS_KWARG
25
30
  GraphQL::Language::Nodes::Field.new(field_alias: key(field_name), name: field_name)
26
31
  else
27
32
  GraphQL::Language::Nodes::Field.new(alias: key(field_name), name: field_name)
@@ -7,18 +7,159 @@ require "json"
7
7
  module GraphQL
8
8
  module Stitching
9
9
  class HttpExecutable
10
- def initialize(url:, headers:{})
10
+ # Builds a new executable for proxying subgraph requests via HTTP.
11
+ # @param url [String] the url of the remote location to proxy.
12
+ # @param headers [Hash] headers to include in upstream requests.
13
+ # @param upload_types [Array<String>, nil] a list of scalar names that represent file uploads. These types extract into multipart forms.
14
+ def initialize(url:, headers: {}, upload_types: nil)
11
15
  @url = url
12
16
  @headers = { "Content-Type" => "application/json" }.merge!(headers)
17
+ @upload_types = upload_types
13
18
  end
14
19
 
15
- def call(_location, document, variables, _context)
16
- response = Net::HTTP.post(
20
+ def call(request, document, variables)
21
+ form_data = extract_multipart_form(request, document, variables)
22
+
23
+ response = if form_data
24
+ send_multipart_form(request, form_data)
25
+ else
26
+ send(request, document, variables)
27
+ end
28
+
29
+ JSON.parse(response.body)
30
+ end
31
+
32
+ # Sends a POST request to the remote location.
33
+ # @param request [Request] the original supergraph request.
34
+ # @param document [String] the location-specific subgraph document to send.
35
+ # @param variables [Hash] a hash of variables specific to the subgraph document.
36
+ def send(_request, document, variables)
37
+ Net::HTTP.post(
17
38
  URI(@url),
18
39
  JSON.generate({ "query" => document, "variables" => variables }),
19
40
  @headers,
20
41
  )
21
- JSON.parse(response.body)
42
+ end
43
+
44
+ # Sends a POST request to the remote location with multipart form data.
45
+ # @param request [Request] the original supergraph request.
46
+ # @param form_data [Hash] a rendered multipart form with an "operations", "map", and file sections.
47
+ def send_multipart_form(_request, form_data)
48
+ uri = URI(@url)
49
+ req = Net::HTTP::Post.new(uri)
50
+ @headers.each_pair do |key, value|
51
+ req[key] = value
52
+ end
53
+
54
+ req.set_form(form_data.to_a, "multipart/form-data")
55
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
56
+ http.request(req)
57
+ end
58
+ end
59
+
60
+ # Extracts multipart upload forms per the spec:
61
+ # https://github.com/jaydenseric/graphql-multipart-request-spec
62
+ # @param request [Request] the original supergraph request.
63
+ # @param document [String] the location-specific subgraph document to send.
64
+ # @param variables [Hash] a hash of variables specific to the subgraph document.
65
+ def extract_multipart_form(request, document, variables)
66
+ return unless @upload_types && request.variable_definitions.any? && variables&.any?
67
+
68
+ files_by_path = {}
69
+
70
+ # extract all upload scalar values mapped by their input path
71
+ variables.each_with_object([]) do |(key, value), path|
72
+ ast_node = request.variable_definitions[key]
73
+ path << key
74
+ extract_ast_node(ast_node, value, files_by_path, path, request) if ast_node
75
+ path.pop
76
+ end
77
+
78
+ return if files_by_path.none?
79
+
80
+ map = {}
81
+ files = files_by_path.values.tap(&:uniq!)
82
+ variables_copy = variables.dup
83
+
84
+ files_by_path.each_key do |path|
85
+ orig = variables
86
+ copy = variables_copy
87
+ path.each_with_index do |key, i|
88
+ if i == path.length - 1
89
+ file_index = files.index(copy[key]).to_s
90
+ map[file_index] ||= []
91
+ map[file_index] << "variables.#{path.join(".")}"
92
+ copy[key] = nil
93
+ elsif orig[key].object_id == copy[key].object_id
94
+ copy[key] = copy[key].dup
95
+ end
96
+ orig = orig[key]
97
+ copy = copy[key]
98
+ end
99
+ end
100
+
101
+ form = {
102
+ "operations" => JSON.generate({
103
+ "query" => document,
104
+ "variables" => variables_copy,
105
+ }),
106
+ "map" => JSON.generate(map),
107
+ }
108
+
109
+ files.each_with_object(form).with_index do |(file, memo), index|
110
+ memo[index.to_s] = file.respond_to?(:tempfile) ? file.tempfile : file
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ def extract_ast_node(ast_node, value, files_by_path, path, request)
117
+ return unless value
118
+
119
+ ast_node = ast_node.of_type while ast_node.is_a?(GraphQL::Language::Nodes::NonNullType)
120
+
121
+ if ast_node.is_a?(GraphQL::Language::Nodes::ListType)
122
+ if value.is_a?(Array)
123
+ value.each_with_index do |val, index|
124
+ path << index
125
+ extract_ast_node(ast_node.of_type, val, files_by_path, path, request)
126
+ path.pop
127
+ end
128
+ end
129
+ elsif @upload_types.include?(ast_node.name)
130
+ files_by_path[path.dup] = value
131
+ else
132
+ type_def = request.supergraph.schema.get_type(ast_node.name)
133
+ extract_type_node(type_def, value, files_by_path, path) if type_def&.kind&.input_object?
134
+ end
135
+ end
136
+
137
+ def extract_type_node(parent_type, value, files_by_path, path)
138
+ return unless value
139
+
140
+ parent_type = Util.unwrap_non_null(parent_type)
141
+
142
+ if parent_type.list?
143
+ if value.is_a?(Array)
144
+ value.each_with_index do |val, index|
145
+ path << index
146
+ extract_type_node(parent_type.of_type, val, files_by_path, path)
147
+ path.pop
148
+ end
149
+ end
150
+ elsif parent_type.kind.input_object?
151
+ if value.is_a?(Enumerable)
152
+ arguments = parent_type.arguments
153
+ value.each do |key, val|
154
+ arg_type = arguments[key]&.type
155
+ path << key
156
+ extract_type_node(arg_type, val, files_by_path, path) if arg_type
157
+ path.pop
158
+ end
159
+ end
160
+ elsif @upload_types.include?(parent_type.graphql_name)
161
+ files_by_path[path.dup] = value
162
+ end
22
163
  end
23
164
  end
24
165
  end
@@ -9,9 +9,9 @@ module GraphQL
9
9
  MUTATION_OP = "mutation"
10
10
  ROOT_INDEX = 0
11
11
 
12
- def initialize(supergraph:, request:)
13
- @supergraph = supergraph
12
+ def initialize(request)
14
13
  @request = request
14
+ @supergraph = request.supergraph
15
15
  @planning_index = ROOT_INDEX
16
16
  @steps_by_entrypoint = {}
17
17
  end
@@ -324,7 +324,7 @@ module GraphQL
324
324
  end
325
325
 
326
326
  if expanded_selections
327
- @supergraph.memoized_schema_possible_types(parent_type.graphql_name).each do |possible_type|
327
+ @request.warden.possible_types(parent_type).each do |possible_type|
328
328
  next unless @supergraph.locations_by_type[possible_type.graphql_name].include?(current_location)
329
329
 
330
330
  type_name = GraphQL::Language::Nodes::TypeName.new(name: possible_type.graphql_name)