graphql-stitching 1.3.0 → 1.4.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.
- checksums.yaml +4 -4
- data/README.md +57 -7
- data/docs/resolver.md +101 -0
- data/lib/graphql/stitching/client.rb +5 -1
- data/lib/graphql/stitching/composer/resolver_config.rb +17 -12
- data/lib/graphql/stitching/composer/validate_interfaces.rb +4 -4
- data/lib/graphql/stitching/composer/validate_resolvers.rb +23 -22
- data/lib/graphql/stitching/composer.rb +77 -83
- data/lib/graphql/stitching/executor/resolver_source.rb +25 -26
- data/lib/graphql/stitching/plan.rb +2 -3
- data/lib/graphql/stitching/planner.rb +11 -22
- data/lib/graphql/stitching/planner_step.rb +1 -1
- data/lib/graphql/stitching/resolver/arguments.rb +284 -0
- data/lib/graphql/stitching/resolver/keys.rb +206 -0
- data/lib/graphql/stitching/resolver.rb +44 -23
- data/lib/graphql/stitching/shaper.rb +3 -3
- data/lib/graphql/stitching/skip_include.rb +1 -1
- data/lib/graphql/stitching/supergraph/key_directive.rb +13 -0
- data/lib/graphql/stitching/supergraph/resolver_directive.rb +4 -5
- data/lib/graphql/stitching/supergraph/to_definition.rb +165 -0
- data/lib/graphql/stitching/supergraph.rb +13 -128
- data/lib/graphql/stitching/util.rb +28 -0
- data/lib/graphql/stitching/version.rb +1 -1
- data/lib/graphql/stitching.rb +2 -1
- metadata +7 -3
- data/lib/graphql/stitching/export_selection.rb +0 -42
@@ -8,9 +8,6 @@ require_relative "./composer/resolver_config"
|
|
8
8
|
module GraphQL
|
9
9
|
module Stitching
|
10
10
|
class Composer
|
11
|
-
class ComposerError < StitchingError; end
|
12
|
-
class ValidationError < ComposerError; end
|
13
|
-
|
14
11
|
# @api private
|
15
12
|
NO_DEFAULT_VALUE = begin
|
16
13
|
class T < GraphQL::Schema::Object
|
@@ -41,7 +38,7 @@ module GraphQL
|
|
41
38
|
attr_reader :mutation_name
|
42
39
|
|
43
40
|
# @api private
|
44
|
-
attr_reader :
|
41
|
+
attr_reader :subgraph_types_by_name_and_location
|
45
42
|
|
46
43
|
# @api private
|
47
44
|
attr_reader :schema_directives
|
@@ -67,8 +64,8 @@ module GraphQL
|
|
67
64
|
@field_map = nil
|
68
65
|
@resolver_map = nil
|
69
66
|
@mapped_type_names = nil
|
70
|
-
@
|
71
|
-
@
|
67
|
+
@subgraph_directives_by_name_and_location = nil
|
68
|
+
@subgraph_types_by_name_and_location = nil
|
72
69
|
@schema_directives = nil
|
73
70
|
end
|
74
71
|
|
@@ -76,8 +73,8 @@ module GraphQL
|
|
76
73
|
reset!
|
77
74
|
schemas, executables = prepare_locations_input(locations_input)
|
78
75
|
|
79
|
-
# "directive_name" => "location" =>
|
80
|
-
@
|
76
|
+
# "directive_name" => "location" => subgraph_directive
|
77
|
+
@subgraph_directives_by_name_and_location = schemas.each_with_object({}) do |(location, schema), memo|
|
81
78
|
(schema.directives.keys - schema.default_directives.keys - GraphQL::Stitching.stitching_directive_names).each do |directive_name|
|
82
79
|
memo[directive_name] ||= {}
|
83
80
|
memo[directive_name][location] = schema.directives[directive_name]
|
@@ -85,44 +82,44 @@ module GraphQL
|
|
85
82
|
end
|
86
83
|
|
87
84
|
# "directive_name" => merged_directive
|
88
|
-
@schema_directives = @
|
85
|
+
@schema_directives = @subgraph_directives_by_name_and_location.each_with_object({}) do |(directive_name, directives_by_location), memo|
|
89
86
|
memo[directive_name] = build_directive(directive_name, directives_by_location)
|
90
87
|
end
|
91
88
|
|
92
89
|
@schema_directives.merge!(GraphQL::Schema.default_directives)
|
93
90
|
|
94
|
-
# "Typename" => "location" =>
|
95
|
-
@
|
96
|
-
raise
|
97
|
-
raise
|
91
|
+
# "Typename" => "location" => subgraph_type
|
92
|
+
@subgraph_types_by_name_and_location = schemas.each_with_object({}) do |(location, schema), memo|
|
93
|
+
raise CompositionError, "Location keys must be strings" unless location.is_a?(String)
|
94
|
+
raise CompositionError, "The subscription operation is not supported." if schema.subscription
|
98
95
|
|
99
96
|
introspection_types = schema.introspection_system.types.keys
|
100
|
-
schema.types.each do |type_name,
|
97
|
+
schema.types.each do |type_name, subgraph_type|
|
101
98
|
next if introspection_types.include?(type_name)
|
102
99
|
|
103
|
-
if type_name == @query_name &&
|
104
|
-
raise
|
105
|
-
elsif type_name == @mutation_name &&
|
106
|
-
raise
|
100
|
+
if type_name == @query_name && subgraph_type != schema.query
|
101
|
+
raise CompositionError, "Query name \"#{@query_name}\" is used by non-query type in #{location} schema."
|
102
|
+
elsif type_name == @mutation_name && subgraph_type != schema.mutation
|
103
|
+
raise CompositionError, "Mutation name \"#{@mutation_name}\" is used by non-mutation type in #{location} schema."
|
107
104
|
end
|
108
105
|
|
109
|
-
type_name = @query_name if
|
110
|
-
type_name = @mutation_name if
|
111
|
-
@mapped_type_names[
|
106
|
+
type_name = @query_name if subgraph_type == schema.query
|
107
|
+
type_name = @mutation_name if subgraph_type == schema.mutation
|
108
|
+
@mapped_type_names[subgraph_type.graphql_name] = type_name if subgraph_type.graphql_name != type_name
|
112
109
|
|
113
110
|
memo[type_name] ||= {}
|
114
|
-
memo[type_name][location] =
|
111
|
+
memo[type_name][location] = subgraph_type
|
115
112
|
end
|
116
113
|
end
|
117
114
|
|
118
115
|
enum_usage = build_enum_usage_map(schemas.values)
|
119
116
|
|
120
117
|
# "Typename" => merged_type
|
121
|
-
schema_types = @
|
118
|
+
schema_types = @subgraph_types_by_name_and_location.each_with_object({}) do |(type_name, types_by_location), memo|
|
122
119
|
kinds = types_by_location.values.map { _1.kind.name }.tap(&:uniq!)
|
123
120
|
|
124
121
|
if kinds.length > 1
|
125
|
-
raise
|
122
|
+
raise CompositionError, "Cannot merge different kinds for `#{type_name}`. Found: #{kinds.join(", ")}."
|
126
123
|
end
|
127
124
|
|
128
125
|
extract_resolvers(type_name, types_by_location) if type_name == @query_name
|
@@ -141,7 +138,7 @@ module GraphQL
|
|
141
138
|
when "INPUT_OBJECT"
|
142
139
|
build_input_object_type(type_name, types_by_location)
|
143
140
|
else
|
144
|
-
raise
|
141
|
+
raise CompositionError, "Unexpected kind encountered for `#{type_name}`. Found: #{kinds.first}."
|
145
142
|
end
|
146
143
|
end
|
147
144
|
|
@@ -184,9 +181,9 @@ module GraphQL
|
|
184
181
|
schema = input[:schema]
|
185
182
|
|
186
183
|
if schema.nil?
|
187
|
-
raise
|
184
|
+
raise CompositionError, "A schema is required for `#{location}` location."
|
188
185
|
elsif !(schema.is_a?(Class) && schema <= GraphQL::Schema)
|
189
|
-
raise
|
186
|
+
raise CompositionError, "The schema for `#{location}` location must be a GraphQL::Schema class."
|
190
187
|
end
|
191
188
|
|
192
189
|
@resolver_configs.merge!(ResolverConfig.extract_directive_assignments(schema, location, input[:stitch]))
|
@@ -234,10 +231,10 @@ module GraphQL
|
|
234
231
|
builder = self
|
235
232
|
|
236
233
|
# "value" => "location" => enum_value
|
237
|
-
enum_values_by_name_location = types_by_location.each_with_object({}) do |(location,
|
238
|
-
|
239
|
-
memo[
|
240
|
-
memo[
|
234
|
+
enum_values_by_name_location = types_by_location.each_with_object({}) do |(location, subgraph_type), memo|
|
235
|
+
subgraph_type.enum_values.each do |subgraph_enum_value|
|
236
|
+
memo[subgraph_enum_value.graphql_name] ||= {}
|
237
|
+
memo[subgraph_enum_value.graphql_name][location] = subgraph_enum_value
|
241
238
|
end
|
242
239
|
end
|
243
240
|
|
@@ -342,14 +339,14 @@ module GraphQL
|
|
342
339
|
# @!visibility private
|
343
340
|
def build_merged_fields(type_name, types_by_location, owner)
|
344
341
|
# "field_name" => "location" => field
|
345
|
-
fields_by_name_location = types_by_location.each_with_object({}) do |(location,
|
342
|
+
fields_by_name_location = types_by_location.each_with_object({}) do |(location, subgraph_type), memo|
|
346
343
|
@field_map[type_name] ||= {}
|
347
|
-
|
348
|
-
@field_map[type_name][
|
349
|
-
@field_map[type_name][
|
344
|
+
subgraph_type.fields.each do |field_name, subgraph_field|
|
345
|
+
@field_map[type_name][subgraph_field.name] ||= []
|
346
|
+
@field_map[type_name][subgraph_field.name] << location
|
350
347
|
|
351
348
|
memo[field_name] ||= {}
|
352
|
-
memo[field_name][location] =
|
349
|
+
memo[field_name][location] = subgraph_field
|
353
350
|
end
|
354
351
|
end
|
355
352
|
|
@@ -375,8 +372,8 @@ module GraphQL
|
|
375
372
|
# @!visibility private
|
376
373
|
def build_merged_arguments(type_name, members_by_location, owner, field_name: nil, directive_name: nil)
|
377
374
|
# "argument_name" => "location" => argument
|
378
|
-
args_by_name_location = members_by_location.each_with_object({}) do |(location,
|
379
|
-
|
375
|
+
args_by_name_location = members_by_location.each_with_object({}) do |(location, subgraph_member), memo|
|
376
|
+
subgraph_member.arguments.each do |argument_name, argument|
|
380
377
|
memo[argument_name] ||= {}
|
381
378
|
memo[argument_name][location] = argument
|
382
379
|
end
|
@@ -388,7 +385,7 @@ module GraphQL
|
|
388
385
|
if arguments_by_location.length != members_by_location.length
|
389
386
|
if value_types.any?(&:non_null?)
|
390
387
|
path = [type_name, field_name, argument_name].compact.join(".")
|
391
|
-
raise
|
388
|
+
raise CompositionError, "Required argument `#{path}` must be defined in all locations." # ...or hidden?
|
392
389
|
end
|
393
390
|
next
|
394
391
|
end
|
@@ -429,8 +426,8 @@ module GraphQL
|
|
429
426
|
# @!scope class
|
430
427
|
# @!visibility private
|
431
428
|
def build_merged_directives(type_name, members_by_location, owner, field_name: nil, argument_name: nil, enum_value: nil)
|
432
|
-
directives_by_name_location = members_by_location.each_with_object({}) do |(location,
|
433
|
-
|
429
|
+
directives_by_name_location = members_by_location.each_with_object({}) do |(location, subgraph_member), memo|
|
430
|
+
subgraph_member.directives.each do |directive|
|
434
431
|
memo[directive.graphql_name] ||= {}
|
435
432
|
memo[directive.graphql_name][location] = directive
|
436
433
|
end
|
@@ -470,18 +467,18 @@ module GraphQL
|
|
470
467
|
|
471
468
|
# @!scope class
|
472
469
|
# @!visibility private
|
473
|
-
def merge_value_types(type_name,
|
470
|
+
def merge_value_types(type_name, subgraph_types, field_name: nil, argument_name: nil)
|
474
471
|
path = [type_name, field_name, argument_name].tap(&:compact!).join(".")
|
475
|
-
alt_structures =
|
472
|
+
alt_structures = subgraph_types.map { Util.flatten_type_structure(_1) }
|
476
473
|
basis_structure = alt_structures.shift
|
477
474
|
|
478
475
|
alt_structures.each do |alt_structure|
|
479
476
|
if alt_structure.length != basis_structure.length
|
480
|
-
raise
|
477
|
+
raise CompositionError, "Cannot compose mixed list structures at `#{path}`."
|
481
478
|
end
|
482
479
|
|
483
480
|
if alt_structure.last.name != basis_structure.last.name
|
484
|
-
raise
|
481
|
+
raise CompositionError, "Cannot compose mixed types at `#{path}`."
|
485
482
|
end
|
486
483
|
end
|
487
484
|
|
@@ -528,63 +525,60 @@ module GraphQL
|
|
528
525
|
# @!scope class
|
529
526
|
# @!visibility private
|
530
527
|
def extract_resolvers(type_name, types_by_location)
|
531
|
-
types_by_location.each do |location,
|
532
|
-
|
533
|
-
resolver_type =
|
534
|
-
resolver_structure = Util.flatten_type_structure(
|
528
|
+
types_by_location.each do |location, subgraph_type|
|
529
|
+
subgraph_type.fields.each do |field_name, subgraph_field|
|
530
|
+
resolver_type = subgraph_field.type.unwrap
|
531
|
+
resolver_structure = Util.flatten_type_structure(subgraph_field.type)
|
535
532
|
resolver_configs = @resolver_configs.fetch("#{location}.#{field_name}", [])
|
536
533
|
|
537
|
-
|
534
|
+
subgraph_field.directives.each do |directive|
|
538
535
|
next unless directive.graphql_name == GraphQL::Stitching.stitch_directive
|
539
536
|
resolver_configs << ResolverConfig.from_kwargs(directive.arguments.keyword_arguments)
|
540
537
|
end
|
541
538
|
|
542
539
|
resolver_configs.each do |config|
|
543
|
-
key_selections = GraphQL.parse("{ #{config.key} }").definitions[0].selections
|
544
|
-
|
545
|
-
if key_selections.length != 1
|
546
|
-
raise ComposerError, "Resolver key at #{type_name}.#{field_name} must specify exactly one key."
|
547
|
-
end
|
548
|
-
|
549
|
-
argument = field_candidate.arguments[key_selections[0].alias]
|
550
|
-
argument ||= if field_candidate.arguments.size == 1
|
551
|
-
field_candidate.arguments.values.first
|
552
|
-
else
|
553
|
-
field_candidate.arguments[config.key]
|
554
|
-
end
|
555
|
-
|
556
|
-
unless argument
|
557
|
-
raise ComposerError, "No resolver argument matched for #{type_name}.#{field_name}. " \
|
558
|
-
"Add an alias to the key that specifies its intended argument, ex: `arg:key`"
|
559
|
-
end
|
560
|
-
|
561
|
-
argument_structure = Util.flatten_type_structure(argument.type)
|
562
|
-
if argument_structure.length != resolver_structure.length
|
563
|
-
raise ComposerError, "Mismatched input/output for #{type_name}.#{field_name}.#{argument.graphql_name} resolver. " \
|
564
|
-
"Arguments must map directly to results."
|
565
|
-
end
|
566
|
-
|
567
540
|
resolver_type_name = if config.type_name
|
568
541
|
if !resolver_type.kind.abstract?
|
569
|
-
raise
|
542
|
+
raise CompositionError, "Resolver config may only specify a type name for abstract resolvers."
|
570
543
|
elsif !resolver_type.possible_types.find { _1.graphql_name == config.type_name }
|
571
|
-
raise
|
544
|
+
raise CompositionError, "Type `#{config.type_name}` is not a possible return type for query `#{field_name}`."
|
572
545
|
end
|
573
546
|
config.type_name
|
574
547
|
else
|
575
548
|
resolver_type.graphql_name
|
576
549
|
end
|
577
550
|
|
551
|
+
key = Resolver.parse_key_with_types(
|
552
|
+
config.key,
|
553
|
+
@subgraph_types_by_name_and_location[resolver_type_name],
|
554
|
+
)
|
555
|
+
|
556
|
+
arguments_format = config.arguments || begin
|
557
|
+
argument = if subgraph_field.arguments.size == 1
|
558
|
+
subgraph_field.arguments.values.first
|
559
|
+
else
|
560
|
+
subgraph_field.arguments[key.default_argument_name]
|
561
|
+
end
|
562
|
+
|
563
|
+
unless argument
|
564
|
+
raise CompositionError, "No resolver argument matched for `#{type_name}.#{field_name}`." \
|
565
|
+
"An argument mapping is required for unmatched names and composite keys."
|
566
|
+
end
|
567
|
+
|
568
|
+
"#{argument.graphql_name}: $.#{key.default_argument_name}"
|
569
|
+
end
|
570
|
+
|
571
|
+
arguments = Resolver.parse_arguments_with_field(arguments_format, subgraph_field)
|
572
|
+
arguments.each { _1.verify_key(key) }
|
573
|
+
|
578
574
|
@resolver_map[resolver_type_name] ||= []
|
579
575
|
@resolver_map[resolver_type_name] << Resolver.new(
|
580
576
|
location: location,
|
581
577
|
type_name: resolver_type_name,
|
582
|
-
|
583
|
-
field: field_candidate.name,
|
584
|
-
arg: argument.graphql_name,
|
585
|
-
arg_type_name: argument.type.unwrap.graphql_name,
|
578
|
+
field: subgraph_field.name,
|
586
579
|
list: resolver_structure.first.list?,
|
587
|
-
|
580
|
+
key: key,
|
581
|
+
arguments: arguments,
|
588
582
|
)
|
589
583
|
end
|
590
584
|
end
|
@@ -619,7 +613,7 @@ module GraphQL
|
|
619
613
|
next unless resolver_type.kind.abstract?
|
620
614
|
|
621
615
|
expanded_types = Util.expand_abstract_type(schema, resolver_type)
|
622
|
-
expanded_types.select { @
|
616
|
+
expanded_types.select { @subgraph_types_by_name_and_location[_1.graphql_name].length > 1 }.each do |expanded_type|
|
623
617
|
@resolver_map[expanded_type.graphql_name] ||= []
|
624
618
|
@resolver_map[expanded_type.graphql_name].push(*@resolver_map[type_name])
|
625
619
|
end
|
@@ -673,7 +667,7 @@ module GraphQL
|
|
673
667
|
@field_map = {}
|
674
668
|
@resolver_map = {}
|
675
669
|
@mapped_type_names = {}
|
676
|
-
@
|
670
|
+
@subgraph_directives_by_name_and_location = nil
|
677
671
|
@schema_directives = nil
|
678
672
|
end
|
679
673
|
end
|
@@ -17,7 +17,7 @@ module GraphQL::Stitching
|
|
17
17
|
|
18
18
|
if op.if_type
|
19
19
|
# operations planned around unused fragment conditions should not trigger requests
|
20
|
-
origin_set.select! { _1[
|
20
|
+
origin_set.select! { _1[Resolver::TYPENAME_EXPORT_NODE.alias] == op.if_type }
|
21
21
|
end
|
22
22
|
|
23
23
|
memo[op] = origin_set if origin_set.any?
|
@@ -53,24 +53,35 @@ module GraphQL::Stitching
|
|
53
53
|
variable_defs = {}
|
54
54
|
query_fields = origin_sets_by_operation.map.with_index do |(op, origin_set), batch_index|
|
55
55
|
variable_defs.merge!(op.variables)
|
56
|
-
resolver = op.resolver
|
56
|
+
resolver = @executor.request.supergraph.resolvers_by_version[op.resolver]
|
57
57
|
|
58
58
|
if resolver.list?
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
59
|
+
arguments = resolver.arguments.map.with_index do |arg, i|
|
60
|
+
if arg.key?
|
61
|
+
variable_name = "_#{batch_index}_key_#{i}".freeze
|
62
|
+
@variables[variable_name] = origin_set.map { arg.build(_1) }
|
63
|
+
variable_defs[variable_name] = arg.to_type_signature
|
64
|
+
"#{arg.name}:$#{variable_name}"
|
65
|
+
else
|
66
|
+
"#{arg.name}:#{arg.value.print}"
|
67
|
+
end
|
63
68
|
end
|
64
69
|
|
65
|
-
|
66
|
-
"_#{batch_index}_result: #{resolver.field}(#{resolver.arg}:$#{variable_name}) #{op.selections}"
|
70
|
+
"_#{batch_index}_result: #{resolver.field}(#{arguments.join(",")}) #{op.selections}"
|
67
71
|
else
|
68
72
|
origin_set.map.with_index do |origin_obj, index|
|
69
|
-
|
70
|
-
|
73
|
+
arguments = resolver.arguments.map.with_index do |arg, i|
|
74
|
+
if arg.key?
|
75
|
+
variable_name = "_#{batch_index}_#{index}_key_#{i}".freeze
|
76
|
+
@variables[variable_name] = arg.build(origin_obj)
|
77
|
+
variable_defs[variable_name] = arg.to_type_signature
|
78
|
+
"#{arg.name}:$#{variable_name}"
|
79
|
+
else
|
80
|
+
"#{arg.name}:#{arg.value.print}"
|
81
|
+
end
|
82
|
+
end
|
71
83
|
|
72
|
-
|
73
|
-
"_#{batch_index}_#{index}_result: #{resolver.field}(#{resolver.arg}:$#{variable_name}) #{op.selections}"
|
84
|
+
"_#{batch_index}_#{index}_result: #{resolver.field}(#{arguments.join(",")}) #{op.selections}"
|
74
85
|
end
|
75
86
|
end
|
76
87
|
end
|
@@ -85,8 +96,7 @@ module GraphQL::Stitching
|
|
85
96
|
end
|
86
97
|
|
87
98
|
if variable_defs.any?
|
88
|
-
|
89
|
-
doc << "(#{variable_str})"
|
99
|
+
doc << "(#{variable_defs.map { |k, v| "$#{k}:#{v}" }.join(",")})"
|
90
100
|
end
|
91
101
|
|
92
102
|
if operation_directives
|
@@ -100,22 +110,11 @@ module GraphQL::Stitching
|
|
100
110
|
end
|
101
111
|
end
|
102
112
|
|
103
|
-
def build_key(key, origin_obj, as_representation: false)
|
104
|
-
if as_representation
|
105
|
-
{
|
106
|
-
"__typename" => origin_obj[ExportSelection.typename_node.alias],
|
107
|
-
key => origin_obj[ExportSelection.key(key)],
|
108
|
-
}
|
109
|
-
else
|
110
|
-
origin_obj[ExportSelection.key(key)]
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
113
|
def merge_results!(origin_sets_by_operation, raw_result)
|
115
114
|
return unless raw_result
|
116
115
|
|
117
116
|
origin_sets_by_operation.each_with_index do |(op, origin_set), batch_index|
|
118
|
-
results = if op.resolver.list?
|
117
|
+
results = if @executor.request.supergraph.resolvers_by_version[op.resolver].list?
|
119
118
|
raw_result["_#{batch_index}_result"]
|
120
119
|
else
|
121
120
|
origin_set.map.with_index { |_, index| raw_result["_#{batch_index}_#{index}_result"] }
|
@@ -27,7 +27,7 @@ module GraphQL
|
|
27
27
|
variables: variables,
|
28
28
|
path: path,
|
29
29
|
if_type: if_type,
|
30
|
-
resolver: resolver
|
30
|
+
resolver: resolver
|
31
31
|
}.tap(&:compact!)
|
32
32
|
end
|
33
33
|
end
|
@@ -36,7 +36,6 @@ module GraphQL
|
|
36
36
|
def from_json(json)
|
37
37
|
ops = json["ops"]
|
38
38
|
ops = ops.map do |op|
|
39
|
-
resolver = op["resolver"]
|
40
39
|
Op.new(
|
41
40
|
step: op["step"],
|
42
41
|
after: op["after"],
|
@@ -46,7 +45,7 @@ module GraphQL
|
|
46
45
|
variables: op["variables"],
|
47
46
|
path: op["path"],
|
48
47
|
if_type: op["if_type"],
|
49
|
-
resolver: resolver
|
48
|
+
resolver: op["resolver"],
|
50
49
|
)
|
51
50
|
end
|
52
51
|
new(ops: ops)
|
@@ -74,7 +74,7 @@ module GraphQL
|
|
74
74
|
resolver: nil
|
75
75
|
)
|
76
76
|
# coalesce repeat parameters into a single entrypoint
|
77
|
-
entrypoint = String.new("#{parent_index}/#{location}/#{parent_type.graphql_name}/#{resolver&.key}")
|
77
|
+
entrypoint = String.new("#{parent_index}/#{location}/#{parent_type.graphql_name}/#{resolver&.key&.to_definition}")
|
78
78
|
path.each { entrypoint << "/#{_1}" }
|
79
79
|
|
80
80
|
step = @steps_by_entrypoint[entrypoint]
|
@@ -153,7 +153,7 @@ module GraphQL
|
|
153
153
|
end
|
154
154
|
|
155
155
|
else
|
156
|
-
raise "Invalid operation type."
|
156
|
+
raise StitchingError, "Invalid operation type."
|
157
157
|
end
|
158
158
|
end
|
159
159
|
|
@@ -173,7 +173,7 @@ module GraphQL
|
|
173
173
|
each_field_in_scope(parent_type, fragment.selections, &block)
|
174
174
|
|
175
175
|
else
|
176
|
-
raise "Unexpected node of type #{node.class.name} in selection set."
|
176
|
+
raise StitchingError, "Unexpected node of type #{node.class.name} in selection set."
|
177
177
|
end
|
178
178
|
end
|
179
179
|
end
|
@@ -199,8 +199,8 @@ module GraphQL
|
|
199
199
|
input_selections.each do |node|
|
200
200
|
case node
|
201
201
|
when GraphQL::Language::Nodes::Field
|
202
|
-
if node.alias&.start_with?(
|
203
|
-
raise StitchingError, %(Alias "#{node.alias}" is not allowed because "#{
|
202
|
+
if node.alias&.start_with?(Resolver::EXPORT_PREFIX)
|
203
|
+
raise StitchingError, %(Alias "#{node.alias}" is not allowed because "#{Resolver::EXPORT_PREFIX}" is a reserved prefix.)
|
204
204
|
elsif node.name == TYPENAME
|
205
205
|
locale_selections << node
|
206
206
|
next
|
@@ -244,6 +244,7 @@ module GraphQL
|
|
244
244
|
fragment = @request.fragment_definitions[node.name]
|
245
245
|
next unless @supergraph.locations_by_type[fragment.type.name].include?(current_location)
|
246
246
|
|
247
|
+
requires_typename = true
|
247
248
|
fragment_type = @supergraph.memoized_schema_types[fragment.type.name]
|
248
249
|
is_same_scope = fragment_type == parent_type
|
249
250
|
selection_set = is_same_scope ? locale_selections : []
|
@@ -251,18 +252,17 @@ module GraphQL
|
|
251
252
|
|
252
253
|
unless is_same_scope
|
253
254
|
locale_selections << GraphQL::Language::Nodes::InlineFragment.new(type: fragment.type, selections: selection_set)
|
254
|
-
requires_typename = true
|
255
255
|
end
|
256
256
|
|
257
257
|
else
|
258
|
-
raise "Unexpected node of type #{node.class.name} in selection set."
|
258
|
+
raise StitchingError, "Unexpected node of type #{node.class.name} in selection set."
|
259
259
|
end
|
260
260
|
end
|
261
261
|
|
262
262
|
# B.4) Add a `__typename` export to abstracts and types that implement
|
263
263
|
# fragments so that resolved type information is available during execution.
|
264
264
|
if requires_typename
|
265
|
-
locale_selections <<
|
265
|
+
locale_selections << Resolver::TYPENAME_EXPORT_NODE
|
266
266
|
end
|
267
267
|
|
268
268
|
if remote_selections
|
@@ -276,20 +276,7 @@ module GraphQL
|
|
276
276
|
routes.each_value do |route|
|
277
277
|
route.reduce(locale_selections) do |parent_selections, resolver|
|
278
278
|
# E.1) Add the key of each resolver query into the prior location's selection set.
|
279
|
-
if resolver.key
|
280
|
-
foreign_key = ExportSelection.key(resolver.key)
|
281
|
-
has_key = false
|
282
|
-
has_typename = false
|
283
|
-
|
284
|
-
parent_selections.each do |node|
|
285
|
-
next unless node.is_a?(GraphQL::Language::Nodes::Field)
|
286
|
-
has_key ||= node.alias == foreign_key
|
287
|
-
has_typename ||= node.alias == ExportSelection.typename_node.alias
|
288
|
-
end
|
289
|
-
|
290
|
-
parent_selections << ExportSelection.key_node(resolver.key) unless has_key
|
291
|
-
parent_selections << ExportSelection.typename_node unless has_typename
|
292
|
-
end
|
279
|
+
parent_selections.push(*resolver.key.export_nodes) if resolver.key
|
293
280
|
|
294
281
|
# E.2) Add a planner step for each new entrypoint location.
|
295
282
|
add_step(
|
@@ -302,6 +289,8 @@ module GraphQL
|
|
302
289
|
).selections
|
303
290
|
end
|
304
291
|
end
|
292
|
+
|
293
|
+
locale_selections.uniq! { _1.alias || _1.name }
|
305
294
|
end
|
306
295
|
|
307
296
|
locale_selections
|