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.
@@ -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