graphql-stitching 1.2.5 → 1.4.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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +67 -17
  3. data/docs/README.md +2 -1
  4. data/docs/mechanics.md +2 -1
  5. data/docs/resolver.md +101 -0
  6. data/lib/graphql/stitching/client.rb +5 -1
  7. data/lib/graphql/stitching/composer/{boundary_config.rb → resolver_config.rb} +18 -13
  8. data/lib/graphql/stitching/composer/validate_interfaces.rb +4 -4
  9. data/lib/graphql/stitching/composer/validate_resolvers.rb +97 -0
  10. data/lib/graphql/stitching/composer.rb +107 -112
  11. data/lib/graphql/stitching/executor/{boundary_source.rb → resolver_source.rb} +40 -32
  12. data/lib/graphql/stitching/executor.rb +3 -3
  13. data/lib/graphql/stitching/plan.rb +3 -4
  14. data/lib/graphql/stitching/planner.rb +30 -41
  15. data/lib/graphql/stitching/planner_step.rb +6 -6
  16. data/lib/graphql/stitching/resolver/arguments.rb +284 -0
  17. data/lib/graphql/stitching/resolver/keys.rb +206 -0
  18. data/lib/graphql/stitching/resolver.rb +70 -0
  19. data/lib/graphql/stitching/shaper.rb +3 -3
  20. data/lib/graphql/stitching/skip_include.rb +1 -1
  21. data/lib/graphql/stitching/supergraph/key_directive.rb +13 -0
  22. data/lib/graphql/stitching/supergraph/resolver_directive.rb +4 -4
  23. data/lib/graphql/stitching/supergraph/to_definition.rb +165 -0
  24. data/lib/graphql/stitching/supergraph.rb +31 -144
  25. data/lib/graphql/stitching/util.rb +28 -0
  26. data/lib/graphql/stitching/version.rb +1 -1
  27. data/lib/graphql/stitching.rb +3 -2
  28. metadata +11 -7
  29. data/lib/graphql/stitching/boundary.rb +0 -29
  30. data/lib/graphql/stitching/composer/validate_boundaries.rb +0 -96
  31. data/lib/graphql/stitching/export_selection.rb +0 -42
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-stitching
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.5
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Greg MacWilliam
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-31 00:00:00.000000000 Z
11
+ date: 2024-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql
@@ -89,6 +89,7 @@ files:
89
89
  - docs/images/stitching.png
90
90
  - docs/mechanics.md
91
91
  - docs/request.md
92
+ - docs/resolver.md
92
93
  - docs/supergraph.md
93
94
  - examples/file_uploads/Gemfile
94
95
  - examples/file_uploads/Procfile
@@ -110,27 +111,30 @@ files:
110
111
  - gemfiles/graphql_2.2.0.gemfile
111
112
  - graphql-stitching.gemspec
112
113
  - lib/graphql/stitching.rb
113
- - lib/graphql/stitching/boundary.rb
114
114
  - lib/graphql/stitching/client.rb
115
115
  - lib/graphql/stitching/composer.rb
116
116
  - lib/graphql/stitching/composer/base_validator.rb
117
- - lib/graphql/stitching/composer/boundary_config.rb
118
- - lib/graphql/stitching/composer/validate_boundaries.rb
117
+ - lib/graphql/stitching/composer/resolver_config.rb
119
118
  - lib/graphql/stitching/composer/validate_interfaces.rb
119
+ - lib/graphql/stitching/composer/validate_resolvers.rb
120
120
  - lib/graphql/stitching/executor.rb
121
- - lib/graphql/stitching/executor/boundary_source.rb
121
+ - lib/graphql/stitching/executor/resolver_source.rb
122
122
  - lib/graphql/stitching/executor/root_source.rb
123
- - lib/graphql/stitching/export_selection.rb
124
123
  - lib/graphql/stitching/http_executable.rb
125
124
  - lib/graphql/stitching/plan.rb
126
125
  - lib/graphql/stitching/planner.rb
127
126
  - lib/graphql/stitching/planner_step.rb
128
127
  - lib/graphql/stitching/request.rb
128
+ - lib/graphql/stitching/resolver.rb
129
+ - lib/graphql/stitching/resolver/arguments.rb
130
+ - lib/graphql/stitching/resolver/keys.rb
129
131
  - lib/graphql/stitching/shaper.rb
130
132
  - lib/graphql/stitching/skip_include.rb
131
133
  - lib/graphql/stitching/supergraph.rb
134
+ - lib/graphql/stitching/supergraph/key_directive.rb
132
135
  - lib/graphql/stitching/supergraph/resolver_directive.rb
133
136
  - lib/graphql/stitching/supergraph/source_directive.rb
137
+ - lib/graphql/stitching/supergraph/to_definition.rb
134
138
  - lib/graphql/stitching/util.rb
135
139
  - lib/graphql/stitching/version.rb
136
140
  homepage: https://github.com/gmac/graphql-stitching-ruby
@@ -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,96 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GraphQL::Stitching
4
- class Composer
5
- class ValidateBoundaries < BaseValidator
6
-
7
- def perform(supergraph, composer)
8
- supergraph.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 supergraph.schema.query == type || supergraph.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 = supergraph.boundaries[type_name]
19
- if boundaries&.any?
20
- validate_as_boundary(supergraph, type, candidate_types_by_location, boundaries)
21
- elsif type.kind.object?
22
- validate_as_shared(supergraph, type, candidate_types_by_location)
23
- end
24
- end
25
- end
26
-
27
- private
28
-
29
- def validate_as_boundary(supergraph, 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(&:key).to_set
45
-
46
- # All non-key fields must be resolvable in at least one boundary location
47
- supergraph.locations_by_type_and_field[type.graphql_name].each do |field_name, locations|
48
- next if boundary_keys.include?(field_name)
49
-
50
- if locations.none? { boundaries_by_location_and_key[_1] }
51
- where = locations.length > 1 ? "one of #{locations.join(", ")} locations" : locations.first
52
- raise Composer::ValidationError, "A boundary query is required for `#{type.graphql_name}` in #{where} to resolve field `#{field_name}`."
53
- end
54
- end
55
-
56
- # All locations of a boundary type must include at least one key field
57
- supergraph.fields_by_type_and_location[type.graphql_name].each do |location, field_names|
58
- if field_names.none? { boundary_keys.include?(_1) }
59
- raise Composer::ValidationError, "A boundary key is required for `#{type.graphql_name}` in #{location} to join with other locations."
60
- end
61
- end
62
-
63
- # verify that all outbound locations can access all inbound locations
64
- resolver_locations = boundaries_by_location_and_key.keys
65
- candidate_types_by_location.each_key do |location|
66
- remote_locations = resolver_locations.reject { _1 == location }
67
- paths = supergraph.route_type_to_locations(type.graphql_name, location, remote_locations)
68
- if paths.length != remote_locations.length || paths.any? { |_loc, path| path.nil? }
69
- raise Composer::ValidationError, "Cannot route `#{type.graphql_name}` boundaries in #{location} to all other locations. "\
70
- "All locations must provide a boundary accessor that uses a conjoining key."
71
- end
72
- end
73
- end
74
-
75
- def validate_as_shared(supergraph, type, candidate_types_by_location)
76
- expected_fields = begin
77
- type.fields.keys.sort
78
- rescue StandardError => e
79
- # bug with inherited interfaces in older versions of GraphQL
80
- if type.interfaces.any? { _1.is_a?(GraphQL::Schema::LateBoundType) }
81
- raise Composer::ComposerError, "Merged interface inheritance requires GraphQL >= v2.0.3"
82
- else
83
- raise e
84
- end
85
- end
86
-
87
- candidate_types_by_location.each do |location, candidate_type|
88
- if candidate_type.fields.keys.sort != expected_fields
89
- raise Composer::ValidationError, "Shared type `#{type.graphql_name}` must have consistent fields across locations, "\
90
- "or else define boundary queries so that its unique fields may be accessed remotely."
91
- end
92
- end
93
- end
94
- end
95
- end
96
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GraphQL
4
- module Stitching
5
- # Builds hidden selection fields added by stitiching code,
6
- # used to request operational data about resolved objects.
7
- class ExportSelection
8
- EXPORT_PREFIX = "_export_"
9
-
10
- class << self
11
- @typename_node = nil
12
-
13
- def key?(name)
14
- return false unless name
15
-
16
- name.start_with?(EXPORT_PREFIX)
17
- end
18
-
19
- def key(name)
20
- "#{EXPORT_PREFIX}#{name}"
21
- end
22
-
23
- # The argument assigning Field.alias changed from
24
- # a generic `alias` hash key to a structured `field_alias` kwarg.
25
- # See https://github.com/rmosolgo/graphql-ruby/pull/4718
26
- FIELD_ALIAS_KWARG = !GraphQL::Language::Nodes::Field.new(field_alias: "a").alias.nil?
27
-
28
- def key_node(field_name)
29
- if FIELD_ALIAS_KWARG
30
- GraphQL::Language::Nodes::Field.new(field_alias: key(field_name), name: field_name)
31
- else
32
- GraphQL::Language::Nodes::Field.new(alias: key(field_name), name: field_name)
33
- end
34
- end
35
-
36
- def typename_node
37
- @typename_node ||= key_node("__typename")
38
- end
39
- end
40
- end
41
- end
42
- end