graphql-stitching 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +10 -21
- 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
|
@@ -255,14 +255,14 @@ module GraphQL
|
|
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
|