graphql-stitching 0.0.1 → 0.2.1
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/.github/workflows/ci.yml +8 -2
- data/.gitignore +2 -0
- data/README.md +15 -16
- data/docs/README.md +1 -1
- data/docs/composer.md +45 -4
- data/docs/executor.md +17 -6
- data/docs/images/library.png +0 -0
- data/docs/planner.md +7 -7
- data/docs/request.md +47 -0
- data/docs/supergraph.md +1 -1
- data/graphql-stitching.gemspec +2 -2
- data/lib/graphql/stitching/composer.rb +89 -7
- data/lib/graphql/stitching/executor.rb +49 -28
- data/lib/graphql/stitching/gateway.rb +18 -13
- data/lib/graphql/stitching/planner.rb +17 -11
- data/lib/graphql/stitching/remote_client.rb +4 -4
- data/lib/graphql/stitching/request.rb +133 -0
- data/lib/graphql/stitching/shaper.rb +60 -56
- data/lib/graphql/stitching/supergraph.rb +7 -6
- data/lib/graphql/stitching/util.rb +9 -1
- data/lib/graphql/stitching/version.rb +1 -1
- data/lib/graphql/stitching.rb +5 -1
- metadata +6 -9
- data/.ruby-version +0 -1
- data/Gemfile.lock +0 -49
- data/docs/document.md +0 -15
- data/docs/shaper.md +0 -20
- data/lib/graphql/stitching/document.rb +0 -59
@@ -7,16 +7,17 @@ module GraphQL
|
|
7
7
|
class Executor
|
8
8
|
|
9
9
|
class RootSource < GraphQL::Dataloader::Source
|
10
|
-
def initialize(executor)
|
10
|
+
def initialize(executor, location)
|
11
11
|
@executor = executor
|
12
|
+
@location = location
|
12
13
|
end
|
13
14
|
|
14
15
|
def fetch(ops)
|
15
16
|
op = ops.first # There should only ever be one per location at a time
|
16
17
|
|
17
18
|
query_document = build_query(op)
|
18
|
-
query_variables = @executor.variables.slice(*op["variables"].keys)
|
19
|
-
result = @executor.supergraph.execute_at_location(op["location"], query_document, query_variables)
|
19
|
+
query_variables = @executor.request.variables.slice(*op["variables"].keys)
|
20
|
+
result = @executor.supergraph.execute_at_location(op["location"], query_document, query_variables, @executor.request.context)
|
20
21
|
@executor.query_count += 1
|
21
22
|
|
22
23
|
@executor.data.merge!(result["data"]) if result["data"]
|
@@ -24,7 +25,8 @@ module GraphQL
|
|
24
25
|
result["errors"].each { _1.delete("locations") }
|
25
26
|
@executor.errors.concat(result["errors"])
|
26
27
|
end
|
27
|
-
|
28
|
+
|
29
|
+
ops.map { op["key"] }
|
28
30
|
end
|
29
31
|
|
30
32
|
def build_query(op)
|
@@ -61,8 +63,8 @@ module GraphQL
|
|
61
63
|
|
62
64
|
if origin_sets_by_operation.any?
|
63
65
|
query_document, variable_names = build_query(origin_sets_by_operation)
|
64
|
-
variables = @executor.variables.slice(*variable_names)
|
65
|
-
raw_result = @executor.supergraph.execute_at_location(@location, query_document, variables)
|
66
|
+
variables = @executor.request.variables.slice(*variable_names)
|
67
|
+
raw_result = @executor.supergraph.execute_at_location(@location, query_document, variables, @executor.request.context)
|
66
68
|
@executor.query_count += 1
|
67
69
|
|
68
70
|
merge_results!(origin_sets_by_operation, raw_result.dig("data"))
|
@@ -165,23 +167,26 @@ module GraphQL
|
|
165
167
|
|
166
168
|
private
|
167
169
|
|
168
|
-
#
|
170
|
+
# traverse forward through origin data, expanding arrays to follow all paths
|
169
171
|
# any errors found for an origin object_id have their path prefixed by the object path
|
170
172
|
def repath_errors!(pathed_errors_by_object_id, forward_path, current_path=[], root=@executor.data)
|
171
|
-
current_path
|
172
|
-
forward_path = forward_path[1..-1]
|
173
|
+
current_path.push(forward_path.shift)
|
173
174
|
scope = root[current_path.last]
|
174
175
|
|
175
176
|
if forward_path.any? && scope.is_a?(Array)
|
176
177
|
scope.each_with_index do |element, index|
|
177
178
|
inner_elements = element.is_a?(Array) ? element.flatten : [element]
|
178
179
|
inner_elements.each do |inner_element|
|
179
|
-
|
180
|
+
current_path << index
|
181
|
+
repath_errors!(pathed_errors_by_object_id, forward_path, current_path, inner_element)
|
182
|
+
current_path.pop
|
180
183
|
end
|
181
184
|
end
|
182
185
|
|
183
186
|
elsif forward_path.any?
|
184
|
-
|
187
|
+
current_path << index
|
188
|
+
repath_errors!(pathed_errors_by_object_id, forward_path, current_path, scope)
|
189
|
+
current_path.pop
|
185
190
|
|
186
191
|
elsif scope.is_a?(Array)
|
187
192
|
scope.each_with_index do |element, index|
|
@@ -196,56 +201,72 @@ module GraphQL
|
|
196
201
|
errors = pathed_errors_by_object_id[scope.object_id]
|
197
202
|
errors.each { _1["path"] = [*current_path, *_1["path"]] } if errors
|
198
203
|
end
|
204
|
+
|
205
|
+
forward_path.unshift(current_path.pop)
|
199
206
|
end
|
200
207
|
end
|
201
208
|
|
202
|
-
attr_reader :supergraph, :
|
209
|
+
attr_reader :supergraph, :request, :data, :errors
|
203
210
|
attr_accessor :query_count
|
204
211
|
|
205
|
-
def initialize(supergraph:, plan:,
|
212
|
+
def initialize(supergraph:, request:, plan:, nonblocking: false)
|
206
213
|
@supergraph = supergraph
|
207
|
-
@
|
214
|
+
@request = request
|
208
215
|
@queue = plan["ops"]
|
209
216
|
@data = {}
|
210
217
|
@errors = []
|
211
218
|
@query_count = 0
|
219
|
+
@exec_cycles = 0
|
212
220
|
@dataloader = GraphQL::Dataloader.new(nonblocking: nonblocking)
|
213
221
|
end
|
214
222
|
|
215
|
-
def perform(
|
223
|
+
def perform(raw: false)
|
216
224
|
exec!
|
217
|
-
|
218
225
|
result = {}
|
219
|
-
result["data"] = @data if @data && @data.length > 0
|
220
|
-
result["errors"] = @errors if @errors.length > 0
|
221
226
|
|
222
|
-
|
227
|
+
if @data && @data.length > 0
|
228
|
+
result["data"] = raw ? @data : GraphQL::Stitching::Shaper.new(
|
229
|
+
schema: @supergraph.schema,
|
230
|
+
request: @request,
|
231
|
+
).perform!(@data)
|
232
|
+
end
|
223
233
|
|
224
|
-
|
234
|
+
if @errors.length > 0
|
235
|
+
result["errors"] = @errors
|
236
|
+
end
|
237
|
+
|
238
|
+
result
|
225
239
|
end
|
226
240
|
|
227
241
|
private
|
228
242
|
|
229
243
|
def exec!(after_keys = [0])
|
244
|
+
if @exec_cycles > @queue.length
|
245
|
+
# sanity check... if we've exceeded queue size, then something went wrong.
|
246
|
+
raise StitchingError, "Too many execution requests attempted."
|
247
|
+
end
|
248
|
+
|
230
249
|
@dataloader.append_job do
|
231
|
-
|
250
|
+
tasks = @queue
|
232
251
|
.select { after_keys.include?(_1["after_key"]) }
|
233
|
-
.group_by { _1["location"] }
|
234
|
-
.map do |location, ops|
|
235
|
-
if
|
236
|
-
@dataloader.with(RootSource, self).request_all(ops)
|
252
|
+
.group_by { [_1["location"], _1["boundary"].nil?] }
|
253
|
+
.map do |(location, root_source), ops|
|
254
|
+
if root_source
|
255
|
+
@dataloader.with(RootSource, self, location).request_all(ops)
|
237
256
|
else
|
238
257
|
@dataloader.with(BoundarySource, self, location).request_all(ops)
|
239
258
|
end
|
240
259
|
end
|
241
260
|
|
242
|
-
|
261
|
+
tasks.each(&method(:exec_task))
|
243
262
|
end
|
263
|
+
|
264
|
+
@exec_cycles += 1
|
244
265
|
@dataloader.run
|
245
266
|
end
|
246
267
|
|
247
|
-
def
|
248
|
-
next_keys =
|
268
|
+
def exec_task(task)
|
269
|
+
next_keys = task.load
|
249
270
|
next_keys.compact!
|
250
271
|
exec!(next_keys) if next_keys.any?
|
251
272
|
end
|
@@ -7,8 +7,6 @@ module GraphQL
|
|
7
7
|
class Gateway
|
8
8
|
class GatewayError < StitchingError; end
|
9
9
|
|
10
|
-
EMPTY_CONTEXT = {}.freeze
|
11
|
-
|
12
10
|
attr_reader :supergraph
|
13
11
|
|
14
12
|
def initialize(locations: nil, supergraph: nil)
|
@@ -25,29 +23,36 @@ module GraphQL
|
|
25
23
|
end
|
26
24
|
end
|
27
25
|
|
28
|
-
def execute(query:, variables: nil, operation_name: nil, context:
|
29
|
-
|
26
|
+
def execute(query:, variables: nil, operation_name: nil, context: nil, validate: true)
|
27
|
+
request = GraphQL::Stitching::Request.new(
|
28
|
+
query,
|
29
|
+
operation_name: operation_name,
|
30
|
+
variables: variables,
|
31
|
+
context: context,
|
32
|
+
)
|
30
33
|
|
31
34
|
if validate
|
32
|
-
validation_errors = @supergraph.schema.validate(document
|
35
|
+
validation_errors = @supergraph.schema.validate(request.document)
|
33
36
|
return error_result(validation_errors) if validation_errors.any?
|
34
37
|
end
|
35
38
|
|
39
|
+
request.prepare!
|
40
|
+
|
36
41
|
begin
|
37
|
-
plan = fetch_plan(
|
42
|
+
plan = fetch_plan(request) do
|
38
43
|
GraphQL::Stitching::Planner.new(
|
39
44
|
supergraph: @supergraph,
|
40
|
-
|
45
|
+
request: request,
|
41
46
|
).perform.to_h
|
42
47
|
end
|
43
48
|
|
44
49
|
GraphQL::Stitching::Executor.new(
|
45
50
|
supergraph: @supergraph,
|
51
|
+
request: request,
|
46
52
|
plan: plan,
|
47
|
-
|
48
|
-
).perform(document)
|
53
|
+
).perform
|
49
54
|
rescue StandardError => e
|
50
|
-
custom_message = @on_error.call(e, context) if @on_error
|
55
|
+
custom_message = @on_error.call(e, request.context) if @on_error
|
51
56
|
error_result([{ "message" => custom_message || "An unexpected error occured." }])
|
52
57
|
end
|
53
58
|
end
|
@@ -91,16 +96,16 @@ module GraphQL
|
|
91
96
|
supergraph
|
92
97
|
end
|
93
98
|
|
94
|
-
def fetch_plan(
|
99
|
+
def fetch_plan(request)
|
95
100
|
if @on_cache_read
|
96
|
-
cached_plan = @on_cache_read.call(
|
101
|
+
cached_plan = @on_cache_read.call(request.digest, request.context)
|
97
102
|
return JSON.parse(cached_plan) if cached_plan
|
98
103
|
end
|
99
104
|
|
100
105
|
plan_json = yield
|
101
106
|
|
102
107
|
if @on_cache_write
|
103
|
-
@on_cache_write.call(
|
108
|
+
@on_cache_write.call(request.digest, JSON.generate(plan_json), request.context)
|
104
109
|
end
|
105
110
|
|
106
111
|
plan_json
|
@@ -6,9 +6,9 @@ module GraphQL
|
|
6
6
|
SUPERGRAPH_LOCATIONS = [Supergraph::LOCATION].freeze
|
7
7
|
TYPENAME_NODE = GraphQL::Language::Nodes::Field.new(alias: "_STITCH_typename", name: "__typename")
|
8
8
|
|
9
|
-
def initialize(supergraph:,
|
9
|
+
def initialize(supergraph:, request:)
|
10
10
|
@supergraph = supergraph
|
11
|
-
@
|
11
|
+
@request = request
|
12
12
|
@sequence_key = 0
|
13
13
|
@operations_by_grouping = {}
|
14
14
|
end
|
@@ -37,7 +37,11 @@ module GraphQL
|
|
37
37
|
extract_locale_selections(location, parent_type, selections, insertion_path, parent_key)
|
38
38
|
end
|
39
39
|
|
40
|
-
grouping =
|
40
|
+
grouping = String.new
|
41
|
+
grouping << after_key.to_s << "/" << location << "/" << parent_type.graphql_name
|
42
|
+
grouping = insertion_path.reduce(grouping) do |memo, segment|
|
43
|
+
memo << "/" << segment
|
44
|
+
end
|
41
45
|
|
42
46
|
if op = @operations_by_grouping[grouping]
|
43
47
|
op.selections += selection_set if selection_set
|
@@ -62,12 +66,12 @@ module GraphQL
|
|
62
66
|
end
|
63
67
|
|
64
68
|
def build_root_operations
|
65
|
-
case @
|
69
|
+
case @request.operation.operation_type
|
66
70
|
when "query"
|
67
71
|
# plan steps grouping all fields by location for async execution
|
68
72
|
parent_type = @supergraph.schema.query
|
69
73
|
|
70
|
-
selections_by_location = @
|
74
|
+
selections_by_location = @request.operation.selections.each_with_object({}) do |node, memo|
|
71
75
|
locations = @supergraph.locations_by_type_and_field[parent_type.graphql_name][node.name] || SUPERGRAPH_LOCATIONS
|
72
76
|
memo[locations.last] ||= []
|
73
77
|
memo[locations.last] << node
|
@@ -82,7 +86,7 @@ module GraphQL
|
|
82
86
|
parent_type = @supergraph.schema.mutation
|
83
87
|
location_groups = []
|
84
88
|
|
85
|
-
@
|
89
|
+
@request.operation.selections.reduce(nil) do |last_location, node|
|
86
90
|
location = @supergraph.locations_by_type_and_field[parent_type.graphql_name][node.name].last
|
87
91
|
if location != last_location
|
88
92
|
location_groups << {
|
@@ -161,8 +165,10 @@ module GraphQL
|
|
161
165
|
if Util.is_leaf_type?(field_type)
|
162
166
|
selections_result << node
|
163
167
|
else
|
164
|
-
|
165
|
-
selection_set, variables = extract_locale_selections(current_location, field_type, node.selections,
|
168
|
+
insertion_path.push(node.alias || node.name)
|
169
|
+
selection_set, variables = extract_locale_selections(current_location, field_type, node.selections, insertion_path, after_key)
|
170
|
+
insertion_path.pop
|
171
|
+
|
166
172
|
selections_result << node.merge(selections: selection_set)
|
167
173
|
variables_result.merge!(variables)
|
168
174
|
end
|
@@ -177,7 +183,7 @@ module GraphQL
|
|
177
183
|
implements_fragments = true
|
178
184
|
|
179
185
|
when GraphQL::Language::Nodes::FragmentSpread
|
180
|
-
fragment = @
|
186
|
+
fragment = @request.fragment_definitions[node.name]
|
181
187
|
next unless @supergraph.locations_by_type[fragment.type.name].include?(current_location)
|
182
188
|
|
183
189
|
fragment_type = @supergraph.schema.types[fragment.type.name]
|
@@ -260,7 +266,7 @@ module GraphQL
|
|
260
266
|
location: location,
|
261
267
|
selections: selections_by_location[location],
|
262
268
|
parent_type: parent_type,
|
263
|
-
insertion_path: insertion_path,
|
269
|
+
insertion_path: insertion_path.dup,
|
264
270
|
boundary: boundary,
|
265
271
|
after_key: after_key,
|
266
272
|
)
|
@@ -289,7 +295,7 @@ module GraphQL
|
|
289
295
|
when GraphQL::Language::Nodes::InputObject
|
290
296
|
extract_node_variables!(argument.value, memo)
|
291
297
|
when GraphQL::Language::Nodes::VariableIdentifier
|
292
|
-
memo[argument.value.name] ||= @
|
298
|
+
memo[argument.value.name] ||= @request.variable_definitions[argument.value.name]
|
293
299
|
end
|
294
300
|
end
|
295
301
|
end
|
@@ -9,14 +9,14 @@ module GraphQL
|
|
9
9
|
class RemoteClient
|
10
10
|
def initialize(url:, headers:{})
|
11
11
|
@url = url
|
12
|
-
@headers = headers
|
12
|
+
@headers = { "Content-Type" => "application/json" }.merge!(headers)
|
13
13
|
end
|
14
14
|
|
15
|
-
def call(
|
15
|
+
def call(_location, document, variables, _context)
|
16
16
|
response = Net::HTTP.post(
|
17
17
|
URI(@url),
|
18
|
-
{ "query" => document, "variables" => variables }
|
19
|
-
|
18
|
+
JSON.generate({ "query" => document, "variables" => variables }),
|
19
|
+
@headers,
|
20
20
|
)
|
21
21
|
JSON.parse(response.body)
|
22
22
|
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module Stitching
|
5
|
+
class Request
|
6
|
+
SUPPORTED_OPERATIONS = ["query", "mutation"].freeze
|
7
|
+
EMPTY_CONTEXT = {}.freeze
|
8
|
+
|
9
|
+
class ApplyRuntimeDirectives < GraphQL::Language::Visitor
|
10
|
+
def initialize(document, variables)
|
11
|
+
@changed = false
|
12
|
+
@variables = variables
|
13
|
+
super(document)
|
14
|
+
end
|
15
|
+
|
16
|
+
def changed?
|
17
|
+
@changed
|
18
|
+
end
|
19
|
+
|
20
|
+
def on_field(node, parent)
|
21
|
+
delete_node = false
|
22
|
+
filtered_directives = if node.directives.any?
|
23
|
+
node.directives.select do |directive|
|
24
|
+
if directive.name == "skip"
|
25
|
+
delete_node = assess_argument_value(directive.arguments.first)
|
26
|
+
false
|
27
|
+
elsif directive.name == "include"
|
28
|
+
delete_node = !assess_argument_value(directive.arguments.first)
|
29
|
+
false
|
30
|
+
else
|
31
|
+
true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
if delete_node
|
37
|
+
@changed = true
|
38
|
+
super(DELETE_NODE, parent)
|
39
|
+
elsif filtered_directives && filtered_directives.length != node.directives.length
|
40
|
+
@changed = true
|
41
|
+
super(node.merge(directives: filtered_directives), parent)
|
42
|
+
else
|
43
|
+
super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def assess_argument_value(arg)
|
50
|
+
if arg.value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
|
51
|
+
return @variables[arg.value.name]
|
52
|
+
end
|
53
|
+
arg.value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_reader :document, :variables, :operation_name, :context
|
58
|
+
|
59
|
+
def initialize(document, operation_name: nil, variables: nil, context: nil)
|
60
|
+
@may_contain_runtime_directives = true
|
61
|
+
|
62
|
+
@document = if document.is_a?(String)
|
63
|
+
@may_contain_runtime_directives = document.include?("@")
|
64
|
+
GraphQL.parse(document)
|
65
|
+
else
|
66
|
+
document
|
67
|
+
end
|
68
|
+
|
69
|
+
@operation_name = operation_name
|
70
|
+
@variables = variables || {}
|
71
|
+
@context = context || EMPTY_CONTEXT
|
72
|
+
end
|
73
|
+
|
74
|
+
def string
|
75
|
+
@string ||= @document.to_query_string
|
76
|
+
end
|
77
|
+
|
78
|
+
def digest
|
79
|
+
@digest ||= Digest::SHA2.hexdigest(string)
|
80
|
+
end
|
81
|
+
|
82
|
+
def operation
|
83
|
+
@operation ||= begin
|
84
|
+
operation_defs = @document.definitions.select do |d|
|
85
|
+
next unless d.is_a?(GraphQL::Language::Nodes::OperationDefinition)
|
86
|
+
next unless SUPPORTED_OPERATIONS.include?(d.operation_type)
|
87
|
+
@operation_name ? d.name == @operation_name : true
|
88
|
+
end
|
89
|
+
|
90
|
+
if operation_defs.length < 1
|
91
|
+
raise GraphQL::ExecutionError, "Invalid root operation."
|
92
|
+
elsif operation_defs.length > 1
|
93
|
+
raise GraphQL::ExecutionError, "An operation name is required when sending multiple operations."
|
94
|
+
end
|
95
|
+
|
96
|
+
operation_defs.first
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def variable_definitions
|
101
|
+
@variable_definitions ||= operation.variables.each_with_object({}) do |v, memo|
|
102
|
+
memo[v.name] = v.type
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def fragment_definitions
|
107
|
+
@fragment_definitions ||= @document.definitions.each_with_object({}) do |d, memo|
|
108
|
+
memo[d.name] = d if d.is_a?(GraphQL::Language::Nodes::FragmentDefinition)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def prepare!
|
113
|
+
operation.variables.each do |v|
|
114
|
+
@variables[v.name] ||= v.default_value
|
115
|
+
end
|
116
|
+
|
117
|
+
return self unless @may_contain_runtime_directives
|
118
|
+
|
119
|
+
visitor = ApplyRuntimeDirectives.new(@document, @variables)
|
120
|
+
@document = visitor.visit
|
121
|
+
|
122
|
+
if visitor.changed?
|
123
|
+
@string = nil
|
124
|
+
@digest = nil
|
125
|
+
@operation = nil
|
126
|
+
@variable_definitions = nil
|
127
|
+
@fragment_definitions = nil
|
128
|
+
end
|
129
|
+
self
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -4,88 +4,92 @@
|
|
4
4
|
module GraphQL
|
5
5
|
module Stitching
|
6
6
|
class Shaper
|
7
|
-
def initialize(
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@result = raw
|
11
|
-
@errors = []
|
7
|
+
def initialize(schema:, request:)
|
8
|
+
@schema = schema
|
9
|
+
@request = request
|
12
10
|
end
|
13
11
|
|
14
|
-
def perform!
|
15
|
-
|
16
|
-
|
17
|
-
# hate doing a second pass, but cannot remove _STITCH_ fields until the fragements are processed
|
18
|
-
clean_entry(@result["data"])
|
19
|
-
end
|
20
|
-
|
21
|
-
if @errors.length > 0
|
22
|
-
@result = [] unless @result.key?("errors")
|
23
|
-
@result["errors"] += @errors
|
24
|
-
end
|
25
|
-
|
26
|
-
@result
|
12
|
+
def perform!(raw)
|
13
|
+
root_type = @schema.public_send(@request.operation.operation_type)
|
14
|
+
resolve_object_scope(raw, root_type, @request.operation.selections)
|
27
15
|
end
|
28
16
|
|
29
17
|
private
|
30
18
|
|
31
|
-
def
|
19
|
+
def resolve_object_scope(raw_object, parent_type, selections, typename = nil)
|
20
|
+
return nil if raw_object.nil?
|
21
|
+
|
22
|
+
typename ||= raw_object["_STITCH_typename"]
|
23
|
+
raw_object.reject! { |k, _v| k.start_with?("_STITCH_") }
|
24
|
+
|
32
25
|
selections.each do |node|
|
33
26
|
case node
|
34
27
|
when GraphQL::Language::Nodes::Field
|
35
|
-
next if node.
|
28
|
+
next if node.name.start_with?("__")
|
36
29
|
|
37
|
-
|
30
|
+
field_name = node.alias || node.name
|
31
|
+
node_type = parent_type.fields[node.name].type
|
32
|
+
named_type = Util.get_named_type_for_field_node(@schema, parent_type, node)
|
33
|
+
|
34
|
+
raw_object[field_name] = if node_type.list?
|
35
|
+
resolve_list_scope(raw_object[field_name], Util.unwrap_non_null(node_type), node.selections)
|
36
|
+
elsif Util.is_leaf_type?(named_type)
|
37
|
+
raw_object[field_name]
|
38
|
+
else
|
39
|
+
resolve_object_scope(raw_object[field_name], named_type, node.selections)
|
40
|
+
end
|
41
|
+
|
42
|
+
return nil if raw_object[field_name].nil? && node_type.non_null?
|
38
43
|
|
39
44
|
when GraphQL::Language::Nodes::InlineFragment
|
40
|
-
next unless
|
41
|
-
fragment_type = @
|
42
|
-
|
45
|
+
next unless typename == node.type.name
|
46
|
+
fragment_type = @schema.types[node.type.name]
|
47
|
+
result = resolve_object_scope(raw_object, fragment_type, node.selections, typename)
|
48
|
+
return nil if result.nil?
|
43
49
|
|
44
50
|
when GraphQL::Language::Nodes::FragmentSpread
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
51
|
+
fragment = @request.fragment_definitions[node.name]
|
52
|
+
fragment_type = @schema.types[fragment.type.name]
|
53
|
+
next unless typename == fragment_type.graphql_name
|
54
|
+
|
55
|
+
result = resolve_object_scope(raw_object, fragment_type, fragment.selections, typename)
|
56
|
+
return nil if result.nil?
|
57
|
+
|
49
58
|
else
|
50
59
|
raise "Unexpected node of type #{node.class.name} in selection set."
|
51
60
|
end
|
52
61
|
end
|
62
|
+
|
63
|
+
raw_object
|
53
64
|
end
|
54
65
|
|
55
|
-
def
|
56
|
-
|
57
|
-
node_type = Util.get_named_type_for_field_node(@supergraph.schema, parent_type, node)
|
66
|
+
def resolve_list_scope(raw_list, current_node_type, selections)
|
67
|
+
return nil if raw_list.nil?
|
58
68
|
|
59
|
-
|
60
|
-
|
61
|
-
|
69
|
+
next_node_type = Util.unwrap_non_null(current_node_type).of_type
|
70
|
+
named_type = Util.get_named_type(next_node_type)
|
71
|
+
contains_null = false
|
62
72
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
73
|
+
resolved_list = raw_list.map! do |raw_list_element|
|
74
|
+
result = if next_node_type.list?
|
75
|
+
resolve_list_scope(raw_list_element, next_node_type, selections)
|
76
|
+
elsif Util.is_leaf_type?(named_type)
|
77
|
+
raw_list_element
|
78
|
+
else
|
79
|
+
resolve_object_scope(raw_list_element, named_type, selections)
|
69
80
|
end
|
70
|
-
elsif ! Util.is_leaf_type?(node_type)
|
71
|
-
munge_entry(child_entry, node.selections, node_type)
|
72
|
-
end
|
73
|
-
end
|
74
81
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
entry.each do |key, value|
|
79
|
-
if key.start_with? "_STITCH_"
|
80
|
-
entry.delete(key)
|
81
|
-
elsif value.is_a?(Array)
|
82
|
-
value.each do |item|
|
83
|
-
clean_entry(item)
|
84
|
-
end
|
85
|
-
elsif value.is_a?(Hash)
|
86
|
-
clean_entry(value)
|
82
|
+
if result.nil?
|
83
|
+
contains_null = true
|
84
|
+
return nil if current_node_type.non_null?
|
87
85
|
end
|
86
|
+
|
87
|
+
result
|
88
88
|
end
|
89
|
+
|
90
|
+
return nil if contains_null && next_node_type.non_null?
|
91
|
+
|
92
|
+
resolved_list
|
89
93
|
end
|
90
94
|
end
|
91
95
|
end
|
@@ -57,27 +57,28 @@ module GraphQL
|
|
57
57
|
def assign_executable(location, executable = nil, &block)
|
58
58
|
executable ||= block
|
59
59
|
unless executable.is_a?(Class) && executable <= GraphQL::Schema
|
60
|
-
raise "A client or block handler must be provided." unless executable
|
61
|
-
raise "A client must be callable" unless executable.respond_to?(:call)
|
60
|
+
raise StitchingError, "A client or block handler must be provided." unless executable
|
61
|
+
raise StitchingError, "A client must be callable" unless executable.respond_to?(:call)
|
62
62
|
end
|
63
63
|
@executables[location] = executable
|
64
64
|
end
|
65
65
|
|
66
|
-
def execute_at_location(location, query, variables)
|
66
|
+
def execute_at_location(location, query, variables, context)
|
67
67
|
executable = executables[location]
|
68
68
|
|
69
69
|
if executable.nil?
|
70
|
-
raise "No executable assigned for #{location} location."
|
70
|
+
raise StitchingError, "No executable assigned for #{location} location."
|
71
71
|
elsif executable.is_a?(Class) && executable <= GraphQL::Schema
|
72
72
|
executable.execute(
|
73
73
|
query: query,
|
74
74
|
variables: variables,
|
75
|
+
context: context,
|
75
76
|
validate: false,
|
76
77
|
)
|
77
78
|
elsif executable.respond_to?(:call)
|
78
|
-
executable.call(location, query, variables)
|
79
|
+
executable.call(location, query, variables, context)
|
79
80
|
else
|
80
|
-
raise "Missing valid executable for #{location} location."
|
81
|
+
raise StitchingError, "Missing valid executable for #{location} location."
|
81
82
|
end
|
82
83
|
end
|
83
84
|
|