graphql-stitching 1.5.0 → 1.5.2
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/.github/workflows/ci.yml +6 -8
- data/Gemfile +1 -0
- data/README.md +60 -20
- data/docs/client.md +6 -0
- data/docs/composer.md +7 -6
- data/docs/federation_entities.md +1 -1
- data/docs/mechanics.md +1 -43
- data/docs/request.md +10 -10
- data/docs/subscriptions.md +11 -11
- data/docs/supergraph.md +5 -5
- data/docs/{resolver.md → type_resolver.md} +3 -3
- data/examples/subscriptions/app/graphql/subscriptions_schema.rb +3 -3
- data/gemfiles/graphql_2.0.0.gemfile +4 -1
- data/gemfiles/graphql_2.1.0.gemfile +4 -1
- data/gemfiles/graphql_2.2.0.gemfile +4 -1
- data/gemfiles/graphql_2.3.0.gemfile +9 -0
- data/graphql-stitching.gemspec +1 -1
- data/lib/graphql/stitching/client.rb +5 -7
- data/lib/graphql/stitching/composer/{resolver_config.rb → type_resolver_config.rb} +2 -2
- data/lib/graphql/stitching/composer/{validate_resolvers.rb → validate_type_resolvers.rb} +1 -1
- data/lib/graphql/stitching/composer.rb +28 -20
- data/lib/graphql/stitching/executor/shaper.rb +4 -4
- data/lib/graphql/stitching/executor/{resolver_source.rb → type_resolver_source.rb} +4 -4
- data/lib/graphql/stitching/executor.rb +5 -5
- data/lib/graphql/stitching/planner/step.rb +1 -1
- data/lib/graphql/stitching/planner.rb +16 -20
- data/lib/graphql/stitching/request/skip_include.rb +1 -1
- data/lib/graphql/stitching/request.rb +3 -7
- data/lib/graphql/stitching/supergraph/to_definition.rb +3 -3
- data/lib/graphql/stitching/supergraph.rb +1 -6
- data/lib/graphql/stitching/{resolver → type_resolver}/arguments.rb +6 -6
- data/lib/graphql/stitching/{resolver → type_resolver}/keys.rb +1 -1
- data/lib/graphql/stitching/{resolver.rb → type_resolver.rb} +4 -4
- data/lib/graphql/stitching/version.rb +1 -1
- data/lib/graphql/stitching.rb +20 -3
- metadata +12 -12
- data/gemfiles/graphql_1.13.9.gemfile +0 -6
@@ -2,8 +2,8 @@
|
|
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_type_resolvers"
|
6
|
+
require_relative "composer/type_resolver_config"
|
7
7
|
|
8
8
|
module GraphQL
|
9
9
|
module Stitching
|
@@ -31,7 +31,7 @@ module GraphQL
|
|
31
31
|
# @api private
|
32
32
|
COMPOSITION_VALIDATORS = [
|
33
33
|
ValidateInterfaces,
|
34
|
-
|
34
|
+
ValidateTypeResolvers,
|
35
35
|
].freeze
|
36
36
|
|
37
37
|
# @return [String] name of the Query type in the composed schema.
|
@@ -168,7 +168,7 @@ module GraphQL
|
|
168
168
|
end
|
169
169
|
|
170
170
|
select_root_field_locations(schema)
|
171
|
-
expand_abstract_resolvers(schema)
|
171
|
+
expand_abstract_resolvers(schema, schemas)
|
172
172
|
|
173
173
|
supergraph = Supergraph.new(
|
174
174
|
schema: schema,
|
@@ -199,8 +199,8 @@ module GraphQL
|
|
199
199
|
raise CompositionError, "The schema for `#{location}` location must be a GraphQL::Schema class."
|
200
200
|
end
|
201
201
|
|
202
|
-
@resolver_configs.merge!(
|
203
|
-
@resolver_configs.merge!(
|
202
|
+
@resolver_configs.merge!(TypeResolverConfig.extract_directive_assignments(schema, location, input[:stitch]))
|
203
|
+
@resolver_configs.merge!(TypeResolverConfig.extract_federation_entities(schema, location))
|
204
204
|
|
205
205
|
schemas[location.to_s] = schema
|
206
206
|
executables[location.to_s] = input[:executable] || schema
|
@@ -403,8 +403,13 @@ module GraphQL
|
|
403
403
|
next
|
404
404
|
end
|
405
405
|
|
406
|
-
# Getting double args sometimes
|
407
|
-
|
406
|
+
# Getting double args sometimes... why?
|
407
|
+
begin
|
408
|
+
next if owner.arguments(GraphQL::Query::NullContext.instance, false).key?(argument_name)
|
409
|
+
rescue ArgumentError
|
410
|
+
# pre- graphql v2.4.5
|
411
|
+
next if owner.arguments.key?(argument_name)
|
412
|
+
end
|
408
413
|
|
409
414
|
kwargs = {}
|
410
415
|
default_values_by_location = arguments_by_location.each_with_object({}) do |(location, argument), memo|
|
@@ -546,13 +551,13 @@ module GraphQL
|
|
546
551
|
|
547
552
|
subgraph_field.directives.each do |directive|
|
548
553
|
next unless directive.graphql_name == GraphQL::Stitching.stitch_directive
|
549
|
-
resolver_configs <<
|
554
|
+
resolver_configs << TypeResolverConfig.from_kwargs(directive.arguments.keyword_arguments)
|
550
555
|
end
|
551
556
|
|
552
557
|
resolver_configs.each do |config|
|
553
558
|
resolver_type_name = if config.type_name
|
554
559
|
if !resolver_type.kind.abstract?
|
555
|
-
raise CompositionError, "
|
560
|
+
raise CompositionError, "Type resolver config may only specify a type name for abstract resolvers."
|
556
561
|
elsif !resolver_type.possible_types.find { _1.graphql_name == config.type_name }
|
557
562
|
raise CompositionError, "Type `#{config.type_name}` is not a possible return type for query `#{field_name}`."
|
558
563
|
end
|
@@ -561,7 +566,7 @@ module GraphQL
|
|
561
566
|
resolver_type.graphql_name
|
562
567
|
end
|
563
568
|
|
564
|
-
key =
|
569
|
+
key = TypeResolver.parse_key_with_types(
|
565
570
|
config.key,
|
566
571
|
@subgraph_types_by_name_and_location[resolver_type_name],
|
567
572
|
)
|
@@ -581,11 +586,11 @@ module GraphQL
|
|
581
586
|
"#{argument.graphql_name}: $.#{key.primitive_name}"
|
582
587
|
end
|
583
588
|
|
584
|
-
arguments =
|
589
|
+
arguments = TypeResolver.parse_arguments_with_field(arguments_format, subgraph_field)
|
585
590
|
arguments.each { _1.verify_key(key) }
|
586
591
|
|
587
592
|
@resolver_map[resolver_type_name] ||= []
|
588
|
-
@resolver_map[resolver_type_name] <<
|
593
|
+
@resolver_map[resolver_type_name] << TypeResolver.new(
|
589
594
|
location: location,
|
590
595
|
type_name: resolver_type_name,
|
591
596
|
field: subgraph_field.name,
|
@@ -620,15 +625,18 @@ module GraphQL
|
|
620
625
|
|
621
626
|
# @!scope class
|
622
627
|
# @!visibility private
|
623
|
-
def expand_abstract_resolvers(
|
628
|
+
def expand_abstract_resolvers(composed_schema, schemas_by_location)
|
624
629
|
@resolver_map.keys.each do |type_name|
|
625
|
-
|
626
|
-
|
630
|
+
next unless composed_schema.get_type(type_name).kind.abstract?
|
631
|
+
|
632
|
+
@resolver_map[type_name].each do |resolver|
|
633
|
+
abstract_type = @subgraph_types_by_name_and_location[type_name][resolver.location]
|
634
|
+
expanded_types = Util.expand_abstract_type(schemas_by_location[resolver.location], abstract_type)
|
627
635
|
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
636
|
+
expanded_types.select { @subgraph_types_by_name_and_location[_1.graphql_name].length > 1 }.each do |impl_type|
|
637
|
+
@resolver_map[impl_type.graphql_name] ||= []
|
638
|
+
@resolver_map[impl_type.graphql_name].push(resolver)
|
639
|
+
end
|
632
640
|
end
|
633
641
|
end
|
634
642
|
end
|
@@ -23,8 +23,8 @@ module GraphQL::Stitching
|
|
23
23
|
def resolve_object_scope(raw_object, parent_type, selections, typename = nil)
|
24
24
|
return nil if raw_object.nil?
|
25
25
|
|
26
|
-
typename ||= raw_object[
|
27
|
-
raw_object.reject! { |key, _v|
|
26
|
+
typename ||= raw_object[TypeResolver::TYPENAME_EXPORT_NODE.alias]
|
27
|
+
raw_object.reject! { |key, _v| TypeResolver.export_key?(key) }
|
28
28
|
|
29
29
|
selections.each do |node|
|
30
30
|
case node
|
@@ -64,7 +64,7 @@ module GraphQL::Stitching
|
|
64
64
|
return nil if result.nil?
|
65
65
|
|
66
66
|
else
|
67
|
-
raise
|
67
|
+
raise DocumentError.new("selection node type")
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
@@ -118,7 +118,7 @@ module GraphQL::Stitching
|
|
118
118
|
def typename_in_type?(typename, type)
|
119
119
|
return true if type.graphql_name == typename
|
120
120
|
|
121
|
-
type.kind.abstract? && @
|
121
|
+
type.kind.abstract? && @supergraph.schema.possible_types(type).any? do |t|
|
122
122
|
t.graphql_name == typename
|
123
123
|
end
|
124
124
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module GraphQL::Stitching
|
4
4
|
class Executor
|
5
|
-
class
|
5
|
+
class TypeResolverSource < GraphQL::Dataloader::Source
|
6
6
|
def initialize(executor, location)
|
7
7
|
@executor = executor
|
8
8
|
@location = location
|
@@ -10,14 +10,14 @@ module GraphQL::Stitching
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def fetch(ops)
|
13
|
-
origin_sets_by_operation = ops.each_with_object({}) do |op, memo|
|
13
|
+
origin_sets_by_operation = ops.each_with_object({}.compare_by_identity) do |op, memo|
|
14
14
|
origin_set = op.path.reduce([@executor.data]) do |set, path_segment|
|
15
15
|
set.flat_map { |obj| obj && obj[path_segment] }.tap(&:compact!)
|
16
16
|
end
|
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[TypeResolver::TYPENAME_EXPORT_NODE.alias] == op.if_type }
|
21
21
|
end
|
22
22
|
|
23
23
|
memo[op] = origin_set if origin_set.any?
|
@@ -86,7 +86,7 @@ module GraphQL::Stitching
|
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
-
doc = String.new(
|
89
|
+
doc = String.new(QUERY_OP) # << resolver fulfillment always uses query
|
90
90
|
|
91
91
|
if operation_name
|
92
92
|
doc << " #{operation_name}"
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "json"
|
4
|
-
require_relative "executor/resolver_source"
|
5
4
|
require_relative "executor/root_source"
|
5
|
+
require_relative "executor/type_resolver_source"
|
6
6
|
require_relative "executor/shaper"
|
7
7
|
|
8
8
|
module GraphQL
|
@@ -27,7 +27,7 @@ module GraphQL
|
|
27
27
|
# Builds a new executor.
|
28
28
|
# @param request [Request] the stitching request to execute.
|
29
29
|
# @param nonblocking [Boolean] specifies if the dataloader should use async concurrency.
|
30
|
-
def initialize(request, data: {}, errors: [], after:
|
30
|
+
def initialize(request, data: {}, errors: [], after: Planner::ROOT_INDEX, nonblocking: false)
|
31
31
|
@request = request
|
32
32
|
@data = data
|
33
33
|
@errors = errors
|
@@ -38,7 +38,7 @@ module GraphQL
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def perform(raw: false)
|
41
|
-
exec!
|
41
|
+
exec!([@after])
|
42
42
|
result = {}
|
43
43
|
|
44
44
|
if @data && @data.length > 0
|
@@ -54,7 +54,7 @@ module GraphQL
|
|
54
54
|
|
55
55
|
private
|
56
56
|
|
57
|
-
def exec!(next_steps
|
57
|
+
def exec!(next_steps)
|
58
58
|
if @exec_cycles > @request.plan.ops.length
|
59
59
|
# sanity check... if we've exceeded queue size, then something went wrong.
|
60
60
|
raise StitchingError, "Too many execution requests attempted."
|
@@ -66,7 +66,7 @@ module GraphQL
|
|
66
66
|
.select { next_steps.include?(_1.after) }
|
67
67
|
.group_by { [_1.location, _1.resolver.nil?] }
|
68
68
|
.map do |(location, root_source), ops|
|
69
|
-
source_class = root_source ? RootSource :
|
69
|
+
source_class = root_source ? RootSource : TypeResolverSource
|
70
70
|
@dataloader.with(source_class, self, location).request_all(ops)
|
71
71
|
end
|
72
72
|
|
@@ -20,7 +20,7 @@ module GraphQL
|
|
20
20
|
def perform
|
21
21
|
build_root_entrypoints
|
22
22
|
expand_abstract_resolvers
|
23
|
-
Plan.new(ops: steps.map(&:to_plan_op))
|
23
|
+
Plan.new(ops: steps.map!(&:to_plan_op))
|
24
24
|
end
|
25
25
|
|
26
26
|
def steps
|
@@ -35,6 +35,7 @@ module GraphQL
|
|
35
35
|
# A) Group all root selections by their preferred entrypoint locations.
|
36
36
|
# A.1) Group query fields by location for parallel execution.
|
37
37
|
# A.2) Partition mutation fields by consecutive location for serial execution.
|
38
|
+
# A.3) Permit exactly one subscription field.
|
38
39
|
#
|
39
40
|
# B) Extract contiguous selections for each entrypoint location.
|
40
41
|
# B.1) Selections on interface types that do not belong to the interface at the
|
@@ -75,9 +76,7 @@ module GraphQL
|
|
75
76
|
resolver: nil
|
76
77
|
)
|
77
78
|
# coalesce repeat parameters into a single entrypoint
|
78
|
-
entrypoint =
|
79
|
-
path.each { entrypoint << "/#{_1}" }
|
80
|
-
|
79
|
+
entrypoint = [parent_index, location, parent_type.graphql_name, resolver&.key&.to_definition, "#", *path].join("/")
|
81
80
|
step = @steps_by_entrypoint[entrypoint]
|
82
81
|
next_index = step ? parent_index : @planning_index += 1
|
83
82
|
|
@@ -107,11 +106,11 @@ module GraphQL
|
|
107
106
|
|
108
107
|
# A) Group all root selections by their preferred entrypoint locations.
|
109
108
|
def build_root_entrypoints
|
109
|
+
parent_type = @supergraph.schema.root_type_for_operation(@request.operation.operation_type)
|
110
|
+
|
110
111
|
case @request.operation.operation_type
|
111
112
|
when QUERY_OP
|
112
113
|
# A.1) Group query fields by location for parallel execution.
|
113
|
-
parent_type = @supergraph.schema.query
|
114
|
-
|
115
114
|
selections_by_location = {}
|
116
115
|
each_field_in_scope(parent_type, @request.operation.selections) do |node|
|
117
116
|
locations = @supergraph.locations_by_type_and_field[parent_type.graphql_name][node.name] || SUPERGRAPH_LOCATIONS
|
@@ -131,8 +130,6 @@ module GraphQL
|
|
131
130
|
|
132
131
|
when MUTATION_OP
|
133
132
|
# A.2) Partition mutation fields by consecutive location for serial execution.
|
134
|
-
parent_type = @supergraph.schema.mutation
|
135
|
-
|
136
133
|
partitions = []
|
137
134
|
each_field_in_scope(parent_type, @request.operation.selections) do |node|
|
138
135
|
next_location = @supergraph.locations_by_type_and_field[parent_type.graphql_name][node.name].first
|
@@ -155,10 +152,9 @@ module GraphQL
|
|
155
152
|
end
|
156
153
|
|
157
154
|
when SUBSCRIPTION_OP
|
158
|
-
|
159
|
-
|
155
|
+
# A.3) Permit exactly one subscription field.
|
160
156
|
each_field_in_scope(parent_type, @request.operation.selections) do |node|
|
161
|
-
raise
|
157
|
+
raise DocumentError.new("root field") unless @steps_by_entrypoint.empty?
|
162
158
|
|
163
159
|
locations = @supergraph.locations_by_type_and_field[parent_type.graphql_name][node.name] || SUPERGRAPH_LOCATIONS
|
164
160
|
add_step(
|
@@ -171,7 +167,7 @@ module GraphQL
|
|
171
167
|
end
|
172
168
|
|
173
169
|
else
|
174
|
-
raise
|
170
|
+
raise DocumentError.new("operation type")
|
175
171
|
end
|
176
172
|
end
|
177
173
|
|
@@ -191,7 +187,7 @@ module GraphQL
|
|
191
187
|
each_field_in_scope(parent_type, fragment.selections, &block)
|
192
188
|
|
193
189
|
else
|
194
|
-
raise
|
190
|
+
raise DocumentError.new("selection node type")
|
195
191
|
end
|
196
192
|
end
|
197
193
|
end
|
@@ -217,8 +213,8 @@ module GraphQL
|
|
217
213
|
input_selections.each do |node|
|
218
214
|
case node
|
219
215
|
when GraphQL::Language::Nodes::Field
|
220
|
-
if node.alias&.start_with?(
|
221
|
-
raise StitchingError, %(Alias "#{node.alias}" is not allowed because "#{
|
216
|
+
if node.alias&.start_with?(TypeResolver::EXPORT_PREFIX)
|
217
|
+
raise StitchingError, %(Alias "#{node.alias}" is not allowed because "#{TypeResolver::EXPORT_PREFIX}" is a reserved prefix.)
|
222
218
|
elsif node.name == TYPENAME
|
223
219
|
locale_selections << node
|
224
220
|
next
|
@@ -273,14 +269,14 @@ module GraphQL
|
|
273
269
|
end
|
274
270
|
|
275
271
|
else
|
276
|
-
raise
|
272
|
+
raise DocumentError.new("selection node type")
|
277
273
|
end
|
278
274
|
end
|
279
275
|
|
280
276
|
# B.4) Add a `__typename` export to abstracts and types that implement
|
281
277
|
# fragments so that resolved type information is available during execution.
|
282
|
-
if requires_typename && !locale_selections.include?(
|
283
|
-
locale_selections <<
|
278
|
+
if requires_typename && !locale_selections.include?(TypeResolver::TYPENAME_EXPORT_NODE)
|
279
|
+
locale_selections << TypeResolver::TYPENAME_EXPORT_NODE
|
284
280
|
end
|
285
281
|
|
286
282
|
if remote_selections
|
@@ -296,7 +292,7 @@ module GraphQL
|
|
296
292
|
# E.1) Add the key of each resolver query into the prior location's selection set.
|
297
293
|
parent_selections.push(*resolver.key.export_nodes) if resolver.key
|
298
294
|
parent_selections.uniq! do |node|
|
299
|
-
export_node = node.is_a?(GraphQL::Language::Nodes::Field) &&
|
295
|
+
export_node = node.is_a?(GraphQL::Language::Nodes::Field) && TypeResolver.export_key?(node.alias)
|
300
296
|
export_node ? node.alias : node.object_id
|
301
297
|
end
|
302
298
|
|
@@ -335,7 +331,7 @@ module GraphQL
|
|
335
331
|
end
|
336
332
|
|
337
333
|
if expanded_selections
|
338
|
-
@
|
334
|
+
@supergraph.schema.possible_types(parent_type).each do |possible_type|
|
339
335
|
next unless @supergraph.locations_by_type[possible_type.graphql_name].include?(current_location)
|
340
336
|
|
341
337
|
type_name = GraphQL::Language::Nodes::TypeName.new(name: possible_type.graphql_name)
|
@@ -26,9 +26,6 @@ module GraphQL
|
|
26
26
|
# @return [Hash] contextual object passed through resolver flows.
|
27
27
|
attr_reader :context
|
28
28
|
|
29
|
-
# @return [GraphQL::Schema::Warden] a visibility warden for this request.
|
30
|
-
attr_reader :warden
|
31
|
-
|
32
29
|
# Creates a new supergraph request.
|
33
30
|
# @param supergraph [Supergraph] supergraph instance that resolves the request.
|
34
31
|
# @param document [String, GraphQL::Language::Nodes::Document] the request string or parsed AST.
|
@@ -58,7 +55,6 @@ module GraphQL
|
|
58
55
|
@variables = variables || {}
|
59
56
|
|
60
57
|
@query = GraphQL::Query.new(@supergraph.schema, document: @document, context: context)
|
61
|
-
@warden = @query.warden
|
62
58
|
@context = @query.context
|
63
59
|
@context[:request] = self
|
64
60
|
end
|
@@ -75,12 +71,12 @@ module GraphQL
|
|
75
71
|
|
76
72
|
# @return [String] a digest of the original document string. Generally faster but less consistent.
|
77
73
|
def digest
|
78
|
-
@digest ||=
|
74
|
+
@digest ||= Stitching.digest.call("#{Stitching::VERSION}/#{string}")
|
79
75
|
end
|
80
76
|
|
81
77
|
# @return [String] a digest of the normalized document string. Slower but more consistent.
|
82
78
|
def normalized_digest
|
83
|
-
@normalized_digest ||=
|
79
|
+
@normalized_digest ||= Stitching.digest.call("#{Stitching::VERSION}/#{normalized_string}")
|
84
80
|
end
|
85
81
|
|
86
82
|
# @return [GraphQL::Language::Nodes::OperationDefinition] The selected root operation for the request.
|
@@ -141,7 +137,7 @@ module GraphQL
|
|
141
137
|
# Validates the request using the combined supergraph schema.
|
142
138
|
# @return [Array<GraphQL::ExecutionError>] an array of static validation errors
|
143
139
|
def validate
|
144
|
-
result = @supergraph.static_validator.validate(@query)
|
140
|
+
result = @supergraph.schema.static_validator.validate(@query)
|
145
141
|
result[:errors]
|
146
142
|
end
|
147
143
|
|
@@ -32,7 +32,7 @@ module GraphQL::Stitching
|
|
32
32
|
end
|
33
33
|
|
34
34
|
key_definitions = locations_by_key.each_with_object({}) do |(key, locations), memo|
|
35
|
-
memo[key] =
|
35
|
+
memo[key] = TypeResolver.parse_key(key, locations)
|
36
36
|
end
|
37
37
|
|
38
38
|
# Collect/build resolver definitions for each type
|
@@ -41,13 +41,13 @@ module GraphQL::Stitching
|
|
41
41
|
|
42
42
|
kwargs = directive.arguments.keyword_arguments
|
43
43
|
resolver_map[type_name] ||= []
|
44
|
-
resolver_map[type_name] <<
|
44
|
+
resolver_map[type_name] << TypeResolver.new(
|
45
45
|
location: kwargs[:location],
|
46
46
|
type_name: kwargs.fetch(:type_name, type_name),
|
47
47
|
field: kwargs[:field],
|
48
48
|
list: kwargs[:list] || false,
|
49
49
|
key: key_definitions[kwargs[:key]],
|
50
|
-
arguments:
|
50
|
+
arguments: TypeResolver.parse_arguments_with_type_defs(kwargs[:arguments], kwargs[:argument_types]),
|
51
51
|
)
|
52
52
|
end
|
53
53
|
|
@@ -50,11 +50,6 @@ module GraphQL
|
|
50
50
|
end.freeze
|
51
51
|
end
|
52
52
|
|
53
|
-
# @return [GraphQL::StaticValidation::Validator] static validator for the supergraph schema.
|
54
|
-
def static_validator
|
55
|
-
@static_validator ||= @schema.static_validator
|
56
|
-
end
|
57
|
-
|
58
53
|
def resolvers_by_version
|
59
54
|
@resolvers_by_version ||= resolvers.values.tap(&:flatten!).each_with_object({}) do |resolver, memo|
|
60
55
|
memo[resolver.version] = resolver
|
@@ -166,7 +161,7 @@ module GraphQL
|
|
166
161
|
if key_count.zero?
|
167
162
|
# nested root scopes have no resolver keys and just return a location
|
168
163
|
goal_locations.each_with_object({}) do |goal_location, memo|
|
169
|
-
memo[goal_location] = [
|
164
|
+
memo[goal_location] = [TypeResolver.new(location: goal_location)]
|
170
165
|
end
|
171
166
|
|
172
167
|
elsif key_count > 1
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module GraphQL::Stitching
|
4
|
-
class
|
4
|
+
class TypeResolver
|
5
5
|
# Defines a single resolver argument structure
|
6
6
|
# @api private
|
7
7
|
class Argument
|
@@ -129,9 +129,9 @@ module GraphQL::Stitching
|
|
129
129
|
end
|
130
130
|
|
131
131
|
def verify_key(arg, key)
|
132
|
-
key_field = value.reduce(
|
132
|
+
key_field = value.reduce(TypeResolver::KeyField.new("", inner: key)) do |field, ns|
|
133
133
|
if ns == TYPENAME
|
134
|
-
|
134
|
+
TypeResolver::KeyField.new(TYPENAME)
|
135
135
|
elsif field
|
136
136
|
field.inner.find { _1.name == ns }
|
137
137
|
end
|
@@ -146,7 +146,7 @@ module GraphQL::Stitching
|
|
146
146
|
|
147
147
|
def build(origin_obj)
|
148
148
|
value.each_with_index.reduce(origin_obj) do |obj, (ns, idx)|
|
149
|
-
obj[idx.zero? ?
|
149
|
+
obj[idx.zero? ? TypeResolver.export_key(ns) : ns]
|
150
150
|
end
|
151
151
|
end
|
152
152
|
|
@@ -174,7 +174,7 @@ module GraphQL::Stitching
|
|
174
174
|
# Parses an argument template string into resolver arguments via schema casting.
|
175
175
|
# @param template [String] the template string to parse.
|
176
176
|
# @param field_def [GraphQL::Schema::FieldDefinition] a field definition providing arguments schema.
|
177
|
-
# @return [[GraphQL::Stitching::
|
177
|
+
# @return [[GraphQL::Stitching::TypeResolver::Argument]] an array of resolver arguments.
|
178
178
|
def parse_arguments_with_field(template, field_def)
|
179
179
|
ast = parse_arg_defs(template)
|
180
180
|
args = build_argument_set(ast, field_def.arguments)
|
@@ -196,7 +196,7 @@ module GraphQL::Stitching
|
|
196
196
|
# Parses an argument template string into resolver arguments via SDL casting.
|
197
197
|
# @param template [String] the template string to parse.
|
198
198
|
# @param type_defs [String] the type definition string declaring argument types.
|
199
|
-
# @return [[GraphQL::Stitching::
|
199
|
+
# @return [[GraphQL::Stitching::TypeResolver::Argument]] an array of resolver arguments.
|
200
200
|
def parse_arguments_with_type_defs(template, type_defs)
|
201
201
|
type_map = parse_type_defs(type_defs)
|
202
202
|
parse_arg_defs(template).map { build_argument(_1, type_struct: type_map[_1.name]) }
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
4
|
-
require_relative "
|
3
|
+
require_relative "type_resolver/arguments"
|
4
|
+
require_relative "type_resolver/keys"
|
5
5
|
|
6
6
|
module GraphQL
|
7
7
|
module Stitching
|
8
8
|
# Defines a type resolver query that provides direct access to an entity type.
|
9
|
-
class
|
9
|
+
class TypeResolver
|
10
10
|
extend ArgumentsParser
|
11
11
|
extend KeysParser
|
12
12
|
|
@@ -47,7 +47,7 @@ module GraphQL
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def version
|
50
|
-
@version ||=
|
50
|
+
@version ||= Stitching.digest.call("#{Stitching::VERSION}/#{as_json.to_json}")
|
51
51
|
end
|
52
52
|
|
53
53
|
def ==(other)
|
data/lib/graphql/stitching.rb
CHANGED
@@ -25,14 +25,31 @@ module GraphQL
|
|
25
25
|
class StitchingError < StandardError; end
|
26
26
|
class CompositionError < StitchingError; end
|
27
27
|
class ValidationError < CompositionError; end
|
28
|
+
class DocumentError < StandardError
|
29
|
+
def initialize(element)
|
30
|
+
super("Invalid #{element} encountered in document")
|
31
|
+
end
|
32
|
+
end
|
28
33
|
|
29
34
|
class << self
|
35
|
+
attr_writer :stitch_directive
|
36
|
+
|
37
|
+
# Proc used to compute digests; uses SHA2 by default.
|
38
|
+
# @returns [Proc] proc used to compute digests.
|
39
|
+
def digest(&block)
|
40
|
+
if block_given?
|
41
|
+
@digest = block
|
42
|
+
else
|
43
|
+
@digest ||= ->(str) { Digest::SHA2.hexdigest(str) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Name of the directive used to mark type resolvers.
|
48
|
+
# @returns [String] name of the type resolver directive.
|
30
49
|
def stitch_directive
|
31
50
|
@stitch_directive ||= "stitch"
|
32
51
|
end
|
33
52
|
|
34
|
-
attr_writer :stitch_directive
|
35
|
-
|
36
53
|
# Names of stitching directives to omit from the composed supergraph.
|
37
54
|
# @returns [Array<String>] list of stitching directive names.
|
38
55
|
def stitching_directive_names
|
@@ -50,6 +67,6 @@ require_relative "stitching/http_executable"
|
|
50
67
|
require_relative "stitching/plan"
|
51
68
|
require_relative "stitching/planner"
|
52
69
|
require_relative "stitching/request"
|
53
|
-
require_relative "stitching/
|
70
|
+
require_relative "stitching/type_resolver"
|
54
71
|
require_relative "stitching/util"
|
55
72
|
require_relative "stitching/version"
|