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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +6 -8
  3. data/Gemfile +1 -0
  4. data/README.md +60 -20
  5. data/docs/client.md +6 -0
  6. data/docs/composer.md +7 -6
  7. data/docs/federation_entities.md +1 -1
  8. data/docs/mechanics.md +1 -43
  9. data/docs/request.md +10 -10
  10. data/docs/subscriptions.md +11 -11
  11. data/docs/supergraph.md +5 -5
  12. data/docs/{resolver.md → type_resolver.md} +3 -3
  13. data/examples/subscriptions/app/graphql/subscriptions_schema.rb +3 -3
  14. data/gemfiles/graphql_2.0.0.gemfile +4 -1
  15. data/gemfiles/graphql_2.1.0.gemfile +4 -1
  16. data/gemfiles/graphql_2.2.0.gemfile +4 -1
  17. data/gemfiles/graphql_2.3.0.gemfile +9 -0
  18. data/graphql-stitching.gemspec +1 -1
  19. data/lib/graphql/stitching/client.rb +5 -7
  20. data/lib/graphql/stitching/composer/{resolver_config.rb → type_resolver_config.rb} +2 -2
  21. data/lib/graphql/stitching/composer/{validate_resolvers.rb → validate_type_resolvers.rb} +1 -1
  22. data/lib/graphql/stitching/composer.rb +28 -20
  23. data/lib/graphql/stitching/executor/shaper.rb +4 -4
  24. data/lib/graphql/stitching/executor/{resolver_source.rb → type_resolver_source.rb} +4 -4
  25. data/lib/graphql/stitching/executor.rb +5 -5
  26. data/lib/graphql/stitching/planner/step.rb +1 -1
  27. data/lib/graphql/stitching/planner.rb +16 -20
  28. data/lib/graphql/stitching/request/skip_include.rb +1 -1
  29. data/lib/graphql/stitching/request.rb +3 -7
  30. data/lib/graphql/stitching/supergraph/to_definition.rb +3 -3
  31. data/lib/graphql/stitching/supergraph.rb +1 -6
  32. data/lib/graphql/stitching/{resolver → type_resolver}/arguments.rb +6 -6
  33. data/lib/graphql/stitching/{resolver → type_resolver}/keys.rb +1 -1
  34. data/lib/graphql/stitching/{resolver.rb → type_resolver.rb} +4 -4
  35. data/lib/graphql/stitching/version.rb +1 -1
  36. data/lib/graphql/stitching.rb +20 -3
  37. metadata +12 -12
  38. 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/validate_resolvers"
6
- require_relative "composer/resolver_config"
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
- ValidateResolvers,
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!(ResolverConfig.extract_directive_assignments(schema, location, input[:stitch]))
203
- @resolver_configs.merge!(ResolverConfig.extract_federation_entities(schema, location))
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 on auto-generated connection types... why?
407
- next if owner.arguments.any? { _1.first == argument_name }
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 << ResolverConfig.from_kwargs(directive.arguments.keyword_arguments)
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, "Resolver config may only specify a type name for abstract resolvers."
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 = Resolver.parse_key_with_types(
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 = Resolver.parse_arguments_with_field(arguments_format, subgraph_field)
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] << Resolver.new(
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(schema)
628
+ def expand_abstract_resolvers(composed_schema, schemas_by_location)
624
629
  @resolver_map.keys.each do |type_name|
625
- resolver_type = schema.types[type_name]
626
- next unless resolver_type.kind.abstract?
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
- expanded_types = Util.expand_abstract_type(schema, resolver_type)
629
- expanded_types.select { @subgraph_types_by_name_and_location[_1.graphql_name].length > 1 }.each do |expanded_type|
630
- @resolver_map[expanded_type.graphql_name] ||= []
631
- @resolver_map[expanded_type.graphql_name].push(*@resolver_map[type_name])
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[Resolver::TYPENAME_EXPORT_NODE.alias]
27
- raw_object.reject! { |key, _v| Resolver.export_key?(key) }
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 StitchingError, "Unexpected node of type #{node.class.name} in selection set."
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? && @request.warden.possible_types(type).any? do |t|
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 ResolverSource < GraphQL::Dataloader::Source
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[Resolver::TYPENAME_EXPORT_NODE.alias] == op.if_type }
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("query") # << resolver fulfillment always uses query
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: 0, nonblocking: false)
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 = [@after])
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 : ResolverSource
69
+ source_class = root_source ? RootSource : TypeResolverSource
70
70
  @dataloader.with(source_class, self, location).request_all(ops)
71
71
  end
72
72
 
@@ -16,7 +16,7 @@ module GraphQL::Stitching
16
16
  parent_type:,
17
17
  index:,
18
18
  after: nil,
19
- operation_type: "query",
19
+ operation_type: QUERY_OP,
20
20
  selections: [],
21
21
  variables: {},
22
22
  path: [],
@@ -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 = String.new("#{parent_index}/#{location}/#{parent_type.graphql_name}/#{resolver&.key&.to_definition}")
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
- parent_type = @supergraph.schema.subscription
159
-
155
+ # A.3) Permit exactly one subscription field.
160
156
  each_field_in_scope(parent_type, @request.operation.selections) do |node|
161
- raise StitchingError, "Too many root fields for subscription." unless @steps_by_entrypoint.empty?
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 StitchingError, "Invalid operation type."
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 StitchingError, "Unexpected node of type #{node.class.name} in selection set."
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?(Resolver::EXPORT_PREFIX)
221
- raise StitchingError, %(Alias "#{node.alias}" is not allowed because "#{Resolver::EXPORT_PREFIX}" is a reserved prefix.)
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 StitchingError, "Unexpected node of type #{node.class.name} in selection set."
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?(Resolver::TYPENAME_EXPORT_NODE)
283
- locale_selections << Resolver::TYPENAME_EXPORT_NODE
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) && Resolver.export_key?(node.alias)
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
- @request.warden.possible_types(parent_type).each do |possible_type|
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)
@@ -40,7 +40,7 @@ module GraphQL::Stitching
40
40
  end
41
41
 
42
42
  if filtered_selections.none?
43
- filtered_selections << Resolver::TYPENAME_EXPORT_NODE
43
+ filtered_selections << TypeResolver::TYPENAME_EXPORT_NODE
44
44
  end
45
45
 
46
46
  if changed
@@ -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 ||= Digest::SHA2.hexdigest(string)
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 ||= Digest::SHA2.hexdigest(normalized_string)
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] = Resolver.parse_key(key, locations)
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] << Resolver.new(
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: Resolver.parse_arguments_with_type_defs(kwargs[:arguments], kwargs[:argument_types]),
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] = [Resolver.new(location: 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 Resolver
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(Resolver::KeyField.new("", inner: key)) do |field, ns|
132
+ key_field = value.reduce(TypeResolver::KeyField.new("", inner: key)) do |field, ns|
133
133
  if ns == TYPENAME
134
- Resolver::KeyField.new(TYPENAME)
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? ? Resolver.export_key(ns) : ns]
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::Resolver::Argument]] an array of resolver arguments.
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::Resolver::Argument]] an array of resolver arguments.
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,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GraphQL::Stitching
4
- class Resolver
4
+ class TypeResolver
5
5
  EXPORT_PREFIX = "_export_"
6
6
 
7
7
  class FieldNode
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "resolver/arguments"
4
- require_relative "resolver/keys"
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 Resolver
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 ||= Digest::SHA2.hexdigest("#{Stitching::VERSION}/#{as_json.to_json}")
50
+ @version ||= Stitching.digest.call("#{Stitching::VERSION}/#{as_json.to_json}")
51
51
  end
52
52
 
53
53
  def ==(other)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  module Stitching
5
- VERSION = "1.5.0"
5
+ VERSION = "1.5.2"
6
6
  end
7
7
  end
@@ -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/resolver"
70
+ require_relative "stitching/type_resolver"
54
71
  require_relative "stitching/util"
55
72
  require_relative "stitching/version"