graphql-stitching 1.2.5 → 1.4.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.
- checksums.yaml +4 -4
- data/README.md +67 -17
- data/docs/README.md +2 -1
- data/docs/mechanics.md +2 -1
- data/docs/resolver.md +101 -0
- data/lib/graphql/stitching/client.rb +5 -1
- data/lib/graphql/stitching/composer/{boundary_config.rb → resolver_config.rb} +18 -13
- data/lib/graphql/stitching/composer/validate_interfaces.rb +4 -4
- data/lib/graphql/stitching/composer/validate_resolvers.rb +97 -0
- data/lib/graphql/stitching/composer.rb +107 -112
- data/lib/graphql/stitching/executor/{boundary_source.rb → resolver_source.rb} +40 -32
- data/lib/graphql/stitching/executor.rb +3 -3
- data/lib/graphql/stitching/plan.rb +3 -4
- data/lib/graphql/stitching/planner.rb +30 -41
- data/lib/graphql/stitching/planner_step.rb +6 -6
- data/lib/graphql/stitching/resolver/arguments.rb +284 -0
- data/lib/graphql/stitching/resolver/keys.rb +206 -0
- data/lib/graphql/stitching/resolver.rb +70 -0
- 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 -4
- data/lib/graphql/stitching/supergraph/to_definition.rb +165 -0
- data/lib/graphql/stitching/supergraph.rb +31 -144
- data/lib/graphql/stitching/util.rb +28 -0
- data/lib/graphql/stitching/version.rb +1 -1
- data/lib/graphql/stitching.rb +3 -2
- metadata +11 -7
- data/lib/graphql/stitching/boundary.rb +0 -29
- data/lib/graphql/stitching/composer/validate_boundaries.rb +0 -96
- data/lib/graphql/stitching/export_selection.rb +0 -42
@@ -2,15 +2,12 @@
|
|
2
2
|
|
3
3
|
require_relative "./composer/base_validator"
|
4
4
|
require_relative "./composer/validate_interfaces"
|
5
|
-
require_relative "./composer/
|
6
|
-
require_relative "./composer/
|
5
|
+
require_relative "./composer/validate_resolvers"
|
6
|
+
require_relative "./composer/resolver_config"
|
7
7
|
|
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
|
@@ -31,7 +28,7 @@ module GraphQL
|
|
31
28
|
# @api private
|
32
29
|
VALIDATORS = [
|
33
30
|
"ValidateInterfaces",
|
34
|
-
"
|
31
|
+
"ValidateResolvers",
|
35
32
|
].freeze
|
36
33
|
|
37
34
|
# @return [String] name of the Query type in the composed schema.
|
@@ -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
|
@@ -62,13 +59,13 @@ module GraphQL
|
|
62
59
|
@default_value_merger = default_value_merger || BASIC_VALUE_MERGER
|
63
60
|
@directive_kwarg_merger = directive_kwarg_merger || BASIC_VALUE_MERGER
|
64
61
|
@root_field_location_selector = root_field_location_selector || BASIC_ROOT_FIELD_LOCATION_SELECTOR
|
65
|
-
@
|
62
|
+
@resolver_configs = {}
|
66
63
|
|
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,47 +82,47 @@ 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
|
129
126
|
|
130
127
|
memo[type_name] = case kinds.first
|
131
128
|
when "SCALAR"
|
@@ -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
|
|
@@ -157,12 +154,12 @@ module GraphQL
|
|
157
154
|
end
|
158
155
|
|
159
156
|
select_root_field_locations(schema)
|
160
|
-
|
157
|
+
expand_abstract_resolvers(schema)
|
161
158
|
|
162
159
|
supergraph = Supergraph.new(
|
163
160
|
schema: schema,
|
164
161
|
fields: @field_map,
|
165
|
-
|
162
|
+
resolvers: @resolver_map,
|
166
163
|
executables: executables,
|
167
164
|
)
|
168
165
|
|
@@ -184,13 +181,13 @@ 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
|
-
@
|
193
|
-
@
|
189
|
+
@resolver_configs.merge!(ResolverConfig.extract_directive_assignments(schema, location, input[:stitch]))
|
190
|
+
@resolver_configs.merge!(ResolverConfig.extract_federation_entities(schema, location))
|
194
191
|
|
195
192
|
schemas[location.to_s] = schema
|
196
193
|
executables[location.to_s] = input[:executable] || schema
|
@@ -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
|
|
@@ -527,63 +524,61 @@ module GraphQL
|
|
527
524
|
|
528
525
|
# @!scope class
|
529
526
|
# @!visibility private
|
530
|
-
def
|
531
|
-
types_by_location.each do |location,
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
527
|
+
def extract_resolvers(type_name, types_by_location)
|
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)
|
532
|
+
resolver_configs = @resolver_configs.fetch("#{location}.#{field_name}", [])
|
533
|
+
|
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
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
elsif field_candidate.arguments[config.key]
|
553
|
-
config.key
|
539
|
+
resolver_configs.each do |config|
|
540
|
+
resolver_type_name = if config.type_name
|
541
|
+
if !resolver_type.kind.abstract?
|
542
|
+
raise CompositionError, "Resolver config may only specify a type name for abstract resolvers."
|
543
|
+
elsif !resolver_type.possible_types.find { _1.graphql_name == config.type_name }
|
544
|
+
raise CompositionError, "Type `#{config.type_name}` is not a possible return type for query `#{field_name}`."
|
545
|
+
end
|
546
|
+
config.type_name
|
547
|
+
else
|
548
|
+
resolver_type.graphql_name
|
554
549
|
end
|
555
550
|
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
end
|
551
|
+
key = Resolver.parse_key_with_types(
|
552
|
+
config.key,
|
553
|
+
@subgraph_types_by_name_and_location[resolver_type_name],
|
554
|
+
)
|
561
555
|
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
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
|
566
562
|
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
elsif !boundary_type.possible_types.find { _1.graphql_name == config.type_name }
|
571
|
-
raise ComposerError, "Type `#{config.type_name}` is not a possible return type for query `#{field_name}`."
|
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."
|
572
566
|
end
|
573
|
-
|
574
|
-
|
575
|
-
boundary_type.graphql_name
|
567
|
+
|
568
|
+
"#{argument.graphql_name}: $.#{key.default_argument_name}"
|
576
569
|
end
|
577
570
|
|
578
|
-
|
579
|
-
|
571
|
+
arguments = Resolver.parse_arguments_with_field(arguments_format, subgraph_field)
|
572
|
+
arguments.each { _1.verify_key(key) }
|
573
|
+
|
574
|
+
@resolver_map[resolver_type_name] ||= []
|
575
|
+
@resolver_map[resolver_type_name] << Resolver.new(
|
580
576
|
location: location,
|
581
|
-
type_name:
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
federation: config.federation,
|
577
|
+
type_name: resolver_type_name,
|
578
|
+
field: subgraph_field.name,
|
579
|
+
list: resolver_structure.first.list?,
|
580
|
+
key: key,
|
581
|
+
arguments: arguments,
|
587
582
|
)
|
588
583
|
end
|
589
584
|
end
|
@@ -612,15 +607,15 @@ module GraphQL
|
|
612
607
|
|
613
608
|
# @!scope class
|
614
609
|
# @!visibility private
|
615
|
-
def
|
616
|
-
@
|
617
|
-
|
618
|
-
next unless
|
619
|
-
|
620
|
-
expanded_types = Util.expand_abstract_type(schema,
|
621
|
-
expanded_types.select { @
|
622
|
-
@
|
623
|
-
@
|
610
|
+
def expand_abstract_resolvers(schema)
|
611
|
+
@resolver_map.keys.each do |type_name|
|
612
|
+
resolver_type = schema.types[type_name]
|
613
|
+
next unless resolver_type.kind.abstract?
|
614
|
+
|
615
|
+
expanded_types = Util.expand_abstract_type(schema, resolver_type)
|
616
|
+
expanded_types.select { @subgraph_types_by_name_and_location[_1.graphql_name].length > 1 }.each do |expanded_type|
|
617
|
+
@resolver_map[expanded_type.graphql_name] ||= []
|
618
|
+
@resolver_map[expanded_type.graphql_name].push(*@resolver_map[type_name])
|
624
619
|
end
|
625
620
|
end
|
626
621
|
end
|
@@ -670,9 +665,9 @@ module GraphQL
|
|
670
665
|
|
671
666
|
def reset!
|
672
667
|
@field_map = {}
|
673
|
-
@
|
668
|
+
@resolver_map = {}
|
674
669
|
@mapped_type_names = {}
|
675
|
-
@
|
670
|
+
@subgraph_directives_by_name_and_location = nil
|
676
671
|
@schema_directives = nil
|
677
672
|
end
|
678
673
|
end
|
@@ -2,10 +2,11 @@
|
|
2
2
|
|
3
3
|
module GraphQL::Stitching
|
4
4
|
class Executor
|
5
|
-
class
|
5
|
+
class ResolverSource < GraphQL::Dataloader::Source
|
6
6
|
def initialize(executor, location)
|
7
7
|
@executor = executor
|
8
8
|
@location = location
|
9
|
+
@variables = {}
|
9
10
|
end
|
10
11
|
|
11
12
|
def fetch(ops)
|
@@ -16,7 +17,7 @@ module GraphQL::Stitching
|
|
16
17
|
|
17
18
|
if op.if_type
|
18
19
|
# operations planned around unused fragment conditions should not trigger requests
|
19
|
-
origin_set.select! { _1[
|
20
|
+
origin_set.select! { _1[Resolver::TYPENAME_EXPORT_NODE.alias] == op.if_type }
|
20
21
|
end
|
21
22
|
|
22
23
|
memo[op] = origin_set if origin_set.any?
|
@@ -28,7 +29,7 @@ module GraphQL::Stitching
|
|
28
29
|
@executor.request.operation_name,
|
29
30
|
@executor.request.operation_directives,
|
30
31
|
)
|
31
|
-
variables = @executor.request.variables.slice(*variable_names)
|
32
|
+
variables = @variables.merge!(@executor.request.variables.slice(*variable_names))
|
32
33
|
raw_result = @executor.request.supergraph.execute_at_location(@location, query_document, variables, @executor.request)
|
33
34
|
@executor.query_count += 1
|
34
35
|
|
@@ -41,36 +42,51 @@ module GraphQL::Stitching
|
|
41
42
|
ops.map { origin_sets_by_operation[_1] ? _1.step : nil }
|
42
43
|
end
|
43
44
|
|
44
|
-
# Builds batched
|
45
|
-
# "query MyOperation_2_3($var:VarType) {
|
46
|
-
# _0_result: list(keys:
|
47
|
-
# _1_0_result: item(key:
|
48
|
-
# _1_1_result: item(key:
|
49
|
-
# _1_2_result: item(key:
|
45
|
+
# Builds batched resolver queries
|
46
|
+
# "query MyOperation_2_3($var:VarType, $_0_key:[ID!]!, $_1_0_key:ID!, $_1_1_key:ID!, $_1_2_key:ID!) {
|
47
|
+
# _0_result: list(keys: $_0_key) { resolverSelections... }
|
48
|
+
# _1_0_result: item(key: $_1_0_key) { resolverSelections... }
|
49
|
+
# _1_1_result: item(key: $_1_1_key) { resolverSelections... }
|
50
|
+
# _1_2_result: item(key: $_1_2_key) { resolverSelections... }
|
50
51
|
# }"
|
51
52
|
def build_document(origin_sets_by_operation, operation_name = nil, operation_directives = nil)
|
52
53
|
variable_defs = {}
|
53
54
|
query_fields = origin_sets_by_operation.map.with_index do |(op, origin_set), batch_index|
|
54
55
|
variable_defs.merge!(op.variables)
|
55
|
-
|
56
|
-
|
57
|
-
if
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
56
|
+
resolver = @executor.request.supergraph.resolvers_by_version[op.resolver]
|
57
|
+
|
58
|
+
if resolver.list?
|
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
|
62
68
|
end
|
63
69
|
|
64
|
-
"_#{batch_index}_result: #{
|
70
|
+
"_#{batch_index}_result: #{resolver.field}(#{arguments.join(",")}) #{op.selections}"
|
65
71
|
else
|
66
72
|
origin_set.map.with_index do |origin_obj, index|
|
67
|
-
|
68
|
-
|
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
|
83
|
+
|
84
|
+
"_#{batch_index}_#{index}_result: #{resolver.field}(#{arguments.join(",")}) #{op.selections}"
|
69
85
|
end
|
70
86
|
end
|
71
87
|
end
|
72
88
|
|
73
|
-
doc = String.new("query") # <<
|
89
|
+
doc = String.new("query") # << resolver fulfillment always uses query
|
74
90
|
|
75
91
|
if operation_name
|
76
92
|
doc << " #{operation_name}"
|
@@ -80,8 +96,7 @@ module GraphQL::Stitching
|
|
80
96
|
end
|
81
97
|
|
82
98
|
if variable_defs.any?
|
83
|
-
|
84
|
-
doc << "(#{variable_str})"
|
99
|
+
doc << "(#{variable_defs.map { |k, v| "$#{k}:#{v}" }.join(",")})"
|
85
100
|
end
|
86
101
|
|
87
102
|
if operation_directives
|
@@ -90,15 +105,8 @@ module GraphQL::Stitching
|
|
90
105
|
|
91
106
|
doc << "{ #{query_fields.join(" ")} }"
|
92
107
|
|
93
|
-
return doc, variable_defs.keys
|
94
|
-
|
95
|
-
|
96
|
-
def build_key(key, origin_obj, federation: false)
|
97
|
-
key_value = JSON.generate(origin_obj[ExportSelection.key(key)])
|
98
|
-
if federation
|
99
|
-
"{ __typename: \"#{origin_obj[ExportSelection.typename_node.alias]}\", #{key}: #{key_value} }"
|
100
|
-
else
|
101
|
-
key_value
|
108
|
+
return doc, variable_defs.keys.tap do |names|
|
109
|
+
names.reject! { @variables.key?(_1) }
|
102
110
|
end
|
103
111
|
end
|
104
112
|
|
@@ -106,7 +114,7 @@ module GraphQL::Stitching
|
|
106
114
|
return unless raw_result
|
107
115
|
|
108
116
|
origin_sets_by_operation.each_with_index do |(op, origin_set), batch_index|
|
109
|
-
results = if op.
|
117
|
+
results = if @executor.request.supergraph.resolvers_by_version[op.resolver].list?
|
110
118
|
raw_result["_#{batch_index}_result"]
|
111
119
|
else
|
112
120
|
origin_set.map.with_index { |_, index| raw_result["_#{batch_index}_#{index}_result"] }
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "json"
|
4
|
-
require_relative "./executor/
|
4
|
+
require_relative "./executor/resolver_source"
|
5
5
|
require_relative "./executor/root_source"
|
6
6
|
|
7
7
|
module GraphQL
|
@@ -55,9 +55,9 @@ module GraphQL
|
|
55
55
|
tasks = @request.plan
|
56
56
|
.ops
|
57
57
|
.select { next_steps.include?(_1.after) }
|
58
|
-
.group_by { [_1.location, _1.
|
58
|
+
.group_by { [_1.location, _1.resolver.nil?] }
|
59
59
|
.map do |(location, root_source), ops|
|
60
|
-
source_type = root_source ? RootSource :
|
60
|
+
source_type = root_source ? RootSource : ResolverSource
|
61
61
|
@dataloader.with(source_type, self, location).request_all(ops)
|
62
62
|
end
|
63
63
|
|
@@ -14,7 +14,7 @@ module GraphQL
|
|
14
14
|
:variables,
|
15
15
|
:path,
|
16
16
|
:if_type,
|
17
|
-
:
|
17
|
+
:resolver,
|
18
18
|
keyword_init: true
|
19
19
|
) do
|
20
20
|
def as_json
|
@@ -27,7 +27,7 @@ module GraphQL
|
|
27
27
|
variables: variables,
|
28
28
|
path: path,
|
29
29
|
if_type: if_type,
|
30
|
-
|
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
|
-
boundary = op["boundary"]
|
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
|
-
|
48
|
+
resolver: op["resolver"],
|
50
49
|
)
|
51
50
|
end
|
52
51
|
new(ops: ops)
|