graphql-stitching 1.2.4 → 1.3.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 +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
|