graphql-stitching 1.2.4 → 1.3.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 +21 -21
- data/docs/README.md +2 -1
- data/docs/mechanics.md +43 -0
- data/lib/graphql/stitching/composer/{boundary_config.rb → resolver_config.rb} +6 -6
- data/lib/graphql/stitching/composer/validate_resolvers.rb +96 -0
- data/lib/graphql/stitching/composer.rb +47 -45
- data/lib/graphql/stitching/executor/{boundary_source.rb → resolver_source.rb} +34 -25
- data/lib/graphql/stitching/executor.rb +3 -3
- data/lib/graphql/stitching/plan.rb +4 -4
- data/lib/graphql/stitching/planner.rb +24 -24
- data/lib/graphql/stitching/planner_step.rb +6 -6
- data/lib/graphql/stitching/resolver.rb +49 -0
- data/lib/graphql/stitching/supergraph/resolver_directive.rb +2 -1
- data/lib/graphql/stitching/supergraph.rb +42 -40
- data/lib/graphql/stitching/version.rb +1 -1
- data/lib/graphql/stitching.rb +1 -1
- metadata +6 -6
- data/lib/graphql/stitching/boundary.rb +0 -29
- data/lib/graphql/stitching/composer/validate_boundaries.rb +0 -91
@@ -1,29 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module GraphQL
|
4
|
-
module Stitching
|
5
|
-
# Defines a boundary query that provides direct access to an entity type.
|
6
|
-
Boundary = Struct.new(
|
7
|
-
:location,
|
8
|
-
:type_name,
|
9
|
-
:key,
|
10
|
-
:field,
|
11
|
-
:arg,
|
12
|
-
:list,
|
13
|
-
:federation,
|
14
|
-
keyword_init: true
|
15
|
-
) do
|
16
|
-
def as_json
|
17
|
-
{
|
18
|
-
location: location,
|
19
|
-
type_name: type_name,
|
20
|
-
key: key,
|
21
|
-
field: field,
|
22
|
-
arg: arg,
|
23
|
-
list: list,
|
24
|
-
federation: federation,
|
25
|
-
}.tap(&:compact!)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
@@ -1,91 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module GraphQL::Stitching
|
4
|
-
class Composer
|
5
|
-
class ValidateBoundaries < BaseValidator
|
6
|
-
|
7
|
-
def perform(ctx, composer)
|
8
|
-
ctx.schema.types.each do |type_name, type|
|
9
|
-
# objects and interfaces that are not the root operation types
|
10
|
-
next unless type.kind.object? || type.kind.interface?
|
11
|
-
next if ctx.schema.query == type || ctx.schema.mutation == type
|
12
|
-
next if type.graphql_name.start_with?("__")
|
13
|
-
|
14
|
-
# multiple subschemas implement the type
|
15
|
-
candidate_types_by_location = composer.candidate_types_by_name_and_location[type_name]
|
16
|
-
next unless candidate_types_by_location.length > 1
|
17
|
-
|
18
|
-
boundaries = ctx.boundaries[type_name]
|
19
|
-
if boundaries&.any?
|
20
|
-
validate_as_boundary(ctx, type, candidate_types_by_location, boundaries)
|
21
|
-
elsif type.kind.object?
|
22
|
-
validate_as_shared(ctx, type, candidate_types_by_location)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def validate_as_boundary(ctx, type, candidate_types_by_location, boundaries)
|
30
|
-
# abstract boundaries are expanded with their concrete implementations, which each get validated. Ignore the abstract itself.
|
31
|
-
return if type.kind.abstract?
|
32
|
-
|
33
|
-
# only one boundary allowed per type/location/key
|
34
|
-
boundaries_by_location_and_key = boundaries.each_with_object({}) do |boundary, memo|
|
35
|
-
if memo.dig(boundary.location, boundary.key)
|
36
|
-
raise Composer::ValidationError, "Multiple boundary queries for `#{type.graphql_name}.#{boundary.key}` "\
|
37
|
-
"found in #{boundary.location}. Limit one boundary query per type and key in each location. "\
|
38
|
-
"Abstract boundaries provide all possible types."
|
39
|
-
end
|
40
|
-
memo[boundary.location] ||= {}
|
41
|
-
memo[boundary.location][boundary.key] = boundary
|
42
|
-
end
|
43
|
-
|
44
|
-
boundary_keys = boundaries.map { _1.key }.uniq
|
45
|
-
key_only_types_by_location = candidate_types_by_location.select do |location, subschema_type|
|
46
|
-
subschema_type.fields.keys.length == 1 && boundary_keys.include?(subschema_type.fields.keys.first)
|
47
|
-
end
|
48
|
-
|
49
|
-
# all locations have a boundary, or else are key-only
|
50
|
-
candidate_types_by_location.each do |location, subschema_type|
|
51
|
-
unless boundaries_by_location_and_key[location] || key_only_types_by_location[location]
|
52
|
-
raise Composer::ValidationError, "A boundary query is required for `#{type.graphql_name}` in #{location} because it provides unique fields."
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
outbound_access_locations = key_only_types_by_location.keys
|
57
|
-
bidirectional_access_locations = candidate_types_by_location.keys - outbound_access_locations
|
58
|
-
|
59
|
-
# verify that all outbound locations can access all inbound locations
|
60
|
-
(outbound_access_locations + bidirectional_access_locations).each do |location|
|
61
|
-
remote_locations = bidirectional_access_locations.reject { _1 == location }
|
62
|
-
paths = ctx.route_type_to_locations(type.graphql_name, location, remote_locations)
|
63
|
-
if paths.length != remote_locations.length || paths.any? { |_loc, path| path.nil? }
|
64
|
-
raise Composer::ValidationError, "Cannot route `#{type.graphql_name}` boundaries in #{location} to all other locations. "\
|
65
|
-
"All locations must provide a boundary accessor that uses a conjoining key."
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def validate_as_shared(ctx, type, candidate_types_by_location)
|
71
|
-
expected_fields = begin
|
72
|
-
type.fields.keys.sort
|
73
|
-
rescue StandardError => e
|
74
|
-
# bug with inherited interfaces in older versions of GraphQL
|
75
|
-
if type.interfaces.any? { _1.is_a?(GraphQL::Schema::LateBoundType) }
|
76
|
-
raise Composer::ComposerError, "Merged interface inheritance requires GraphQL >= v2.0.3"
|
77
|
-
else
|
78
|
-
raise e
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
candidate_types_by_location.each do |location, subschema_type|
|
83
|
-
if subschema_type.fields.keys.sort != expected_fields
|
84
|
-
raise Composer::ValidationError, "Shared type `#{type.graphql_name}` must have consistent fields across locations, "\
|
85
|
-
"or else define boundary queries so that its unique fields may be accessed remotely."
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|