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