graphql 2.5.18 → 2.5.20
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/lib/generators/graphql/detailed_trace_generator.rb +77 -0
- data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
- data/lib/graphql/dashboard/application_controller.rb +41 -0
- data/lib/graphql/dashboard/landings_controller.rb +9 -0
- data/lib/graphql/dashboard/statics_controller.rb +31 -0
- data/lib/graphql/dashboard/subscriptions.rb +2 -1
- data/lib/graphql/dashboard.rb +9 -74
- data/lib/graphql/dataloader/null_dataloader.rb +7 -3
- data/lib/graphql/execution/batching/field_compatibility.rb +150 -0
- data/lib/graphql/execution/batching/field_resolve_step.rb +408 -0
- data/lib/graphql/execution/batching/prepare_object_step.rb +112 -0
- data/lib/graphql/execution/batching/runner.rb +352 -0
- data/lib/graphql/execution/batching/selections_step.rb +37 -0
- data/lib/graphql/execution/batching.rb +62 -0
- data/lib/graphql/execution_error.rb +13 -10
- data/lib/graphql/introspection/directive_type.rb +7 -3
- data/lib/graphql/introspection/entry_points.rb +6 -2
- data/lib/graphql/introspection/enum_value_type.rb +5 -5
- data/lib/graphql/introspection/field_type.rb +13 -5
- data/lib/graphql/introspection/input_value_type.rb +21 -13
- data/lib/graphql/introspection/type_type.rb +64 -28
- data/lib/graphql/invalid_null_error.rb +11 -5
- data/lib/graphql/query/context.rb +3 -2
- data/lib/graphql/query/null_context.rb +9 -3
- data/lib/graphql/schema/argument.rb +4 -0
- data/lib/graphql/schema/build_from_definition.rb +26 -6
- data/lib/graphql/schema/field.rb +76 -1
- data/lib/graphql/schema/member/has_dataloader.rb +9 -0
- data/lib/graphql/schema/member/has_fields.rb +3 -0
- data/lib/graphql/schema/resolver.rb +3 -1
- data/lib/graphql/schema/visibility.rb +1 -1
- data/lib/graphql/schema.rb +33 -7
- data/lib/graphql/subscriptions.rb +1 -1
- data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
- data/lib/graphql/tracing/detailed_trace.rb +20 -5
- data/lib/graphql/tracing/perfetto_trace.rb +48 -2
- data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
- data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
- data/lib/graphql/types/relay/has_node_field.rb +5 -8
- data/lib/graphql/types/relay/has_nodes_field.rb +5 -8
- data/lib/graphql/unauthorized_error.rb +5 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +3 -0
- metadata +14 -2
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphQL
|
|
3
|
+
module Execution
|
|
4
|
+
module Batching
|
|
5
|
+
class Runner
|
|
6
|
+
def initialize(multiplex)
|
|
7
|
+
@multiplex = multiplex
|
|
8
|
+
@schema = multiplex.schema
|
|
9
|
+
@steps_queue = []
|
|
10
|
+
@runtime_types_at_result = {}.compare_by_identity
|
|
11
|
+
@static_types_at_result = {}.compare_by_identity
|
|
12
|
+
@selected_operation = nil
|
|
13
|
+
@dataloader = multiplex.context[:dataloader] ||= @schema.dataloader_class.new
|
|
14
|
+
@resolves_lazies = @schema.resolves_lazies?
|
|
15
|
+
@field_resolve_step_class = @schema.uses_raw_value? ? RawValueFieldResolveStep : FieldResolveStep
|
|
16
|
+
@authorizes = {}.compare_by_identity
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def add_step(step)
|
|
20
|
+
@dataloader.append_job(step)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
attr_reader :steps_queue, :schema, :variables, :static_types_at_result, :runtime_types_at_result, :dataloader, :resolves_lazies, :authorizes
|
|
24
|
+
|
|
25
|
+
def execute
|
|
26
|
+
Fiber[:__graphql_current_multiplex] = @multiplex
|
|
27
|
+
isolated_steps = [[]]
|
|
28
|
+
trace = @multiplex.current_trace
|
|
29
|
+
queries = @multiplex.queries
|
|
30
|
+
multiplex_analyzers = @schema.multiplex_analyzers
|
|
31
|
+
if @multiplex.max_complexity
|
|
32
|
+
multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
trace.execute_multiplex(multiplex: @multiplex) do
|
|
36
|
+
trace.begin_analyze_multiplex(@multiplex, multiplex_analyzers)
|
|
37
|
+
@schema.analysis_engine.analyze_multiplex(@multiplex, multiplex_analyzers)
|
|
38
|
+
trace.end_analyze_multiplex(@multiplex, multiplex_analyzers)
|
|
39
|
+
|
|
40
|
+
results = []
|
|
41
|
+
queries.each do |query|
|
|
42
|
+
if query.validate && !query.valid?
|
|
43
|
+
results << {
|
|
44
|
+
"errors" => query.static_errors.map(&:to_h)
|
|
45
|
+
}
|
|
46
|
+
next
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
selected_operation = query.document.definitions.first # TODO select named operation
|
|
50
|
+
data = {}
|
|
51
|
+
|
|
52
|
+
root_type = case selected_operation.operation_type
|
|
53
|
+
when nil, "query"
|
|
54
|
+
@schema.query
|
|
55
|
+
when "mutation"
|
|
56
|
+
@schema.mutation
|
|
57
|
+
when "subscription"
|
|
58
|
+
@schema.subscription
|
|
59
|
+
else
|
|
60
|
+
raise ArgumentError, "Unknown operation type: #{selected_operation.operation_type.inspect}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
auth_check = schema.sync_lazy(root_type.authorized?(query.root_value, query.context))
|
|
64
|
+
root_value = if auth_check
|
|
65
|
+
query.root_value
|
|
66
|
+
else
|
|
67
|
+
begin
|
|
68
|
+
auth_err = GraphQL::UnauthorizedError.new(object: query.root_value, type: root_type, context: query.context)
|
|
69
|
+
new_val = schema.unauthorized_object(auth_err)
|
|
70
|
+
if new_val
|
|
71
|
+
auth_check = true
|
|
72
|
+
end
|
|
73
|
+
new_val
|
|
74
|
+
rescue GraphQL::ExecutionError => ex_err
|
|
75
|
+
# The old runtime didn't add path and ast_nodes to this
|
|
76
|
+
query.context.add_error(ex_err)
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if !auth_check
|
|
82
|
+
results << {}
|
|
83
|
+
next
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
results << { "data" => data }
|
|
87
|
+
|
|
88
|
+
case selected_operation.operation_type
|
|
89
|
+
when nil, "query"
|
|
90
|
+
isolated_steps[0] << SelectionsStep.new(
|
|
91
|
+
parent_type: root_type,
|
|
92
|
+
selections: selected_operation.selections,
|
|
93
|
+
objects: [root_value],
|
|
94
|
+
results: [data],
|
|
95
|
+
path: EmptyObjects::EMPTY_ARRAY,
|
|
96
|
+
runner: self,
|
|
97
|
+
query: query,
|
|
98
|
+
)
|
|
99
|
+
when "mutation"
|
|
100
|
+
fields = {}
|
|
101
|
+
gather_selections(root_type, selected_operation.selections, nil, query, {}, into: fields)
|
|
102
|
+
fields.each_value do |field_resolve_step|
|
|
103
|
+
isolated_steps << [SelectionsStep.new(
|
|
104
|
+
parent_type: root_type,
|
|
105
|
+
selections: field_resolve_step.ast_nodes || Array(field_resolve_step.ast_node),
|
|
106
|
+
objects: [root_value],
|
|
107
|
+
results: [data],
|
|
108
|
+
path: EmptyObjects::EMPTY_ARRAY,
|
|
109
|
+
runner: self,
|
|
110
|
+
query: query,
|
|
111
|
+
)]
|
|
112
|
+
end
|
|
113
|
+
when "subscription"
|
|
114
|
+
raise ArgumentError, "TODO implement subscriptions"
|
|
115
|
+
else
|
|
116
|
+
raise ArgumentError, "Unhandled operation type: #{operation.operation_type.inspect}"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
@static_types_at_result[data] = root_type
|
|
120
|
+
@runtime_types_at_result[data] = root_type
|
|
121
|
+
|
|
122
|
+
# TODO This is stupid but makes multiplex_spec.rb pass
|
|
123
|
+
trace.execute_query(query: query) do
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
while (next_isolated_steps = isolated_steps.shift)
|
|
128
|
+
next_isolated_steps.each do |step|
|
|
129
|
+
add_step(step)
|
|
130
|
+
end
|
|
131
|
+
@dataloader.run
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# TODO This is stupid but makes multiplex_spec.rb pass
|
|
135
|
+
trace.execute_query_lazy(query: nil, multiplex: @multiplex) do
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
queries.each_with_index.map do |query, idx|
|
|
139
|
+
result = results[idx]
|
|
140
|
+
fin_result = if query.context.errors.empty?
|
|
141
|
+
result
|
|
142
|
+
else
|
|
143
|
+
data = result["data"]
|
|
144
|
+
data = propagate_errors(data, query)
|
|
145
|
+
errors = []
|
|
146
|
+
query.context.errors.each do |err|
|
|
147
|
+
if err.respond_to?(:to_h)
|
|
148
|
+
errors << err.to_h
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
res_h = {}
|
|
152
|
+
if !errors.empty?
|
|
153
|
+
res_h["errors"] = errors
|
|
154
|
+
end
|
|
155
|
+
res_h["data"] = data
|
|
156
|
+
res_h
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
GraphQL::Query::Result.new(query: query, values: fin_result)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
ensure
|
|
163
|
+
Fiber[:__graphql_current_multiplex] = nil
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def gather_selections(type_defn, ast_selections, selections_step, query, prototype_result, into:)
|
|
167
|
+
ast_selections.each do |ast_selection|
|
|
168
|
+
next if !directives_include?(query, ast_selection)
|
|
169
|
+
case ast_selection
|
|
170
|
+
when GraphQL::Language::Nodes::Field
|
|
171
|
+
key = ast_selection.alias || ast_selection.name
|
|
172
|
+
step = into[key] ||= begin
|
|
173
|
+
prototype_result[key] = nil
|
|
174
|
+
|
|
175
|
+
@field_resolve_step_class.new(
|
|
176
|
+
selections_step: selections_step,
|
|
177
|
+
key: key,
|
|
178
|
+
parent_type: type_defn,
|
|
179
|
+
runner: self,
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
step.append_selection(ast_selection)
|
|
183
|
+
when GraphQL::Language::Nodes::InlineFragment
|
|
184
|
+
type_condition = ast_selection.type&.name
|
|
185
|
+
if type_condition.nil? || type_condition_applies?(query.context, type_defn, type_condition)
|
|
186
|
+
gather_selections(type_defn, ast_selection.selections, selections_step, query, prototype_result, into: into)
|
|
187
|
+
end
|
|
188
|
+
when GraphQL::Language::Nodes::FragmentSpread
|
|
189
|
+
fragment_definition = query.document.definitions.find { |defn| defn.is_a?(GraphQL::Language::Nodes::FragmentDefinition) && defn.name == ast_selection.name }
|
|
190
|
+
type_condition = fragment_definition.type.name
|
|
191
|
+
if type_condition_applies?(query.context, type_defn, type_condition)
|
|
192
|
+
gather_selections(type_defn, fragment_definition.selections, selections_step, query, prototype_result, into: into)
|
|
193
|
+
end
|
|
194
|
+
else
|
|
195
|
+
raise ArgumentError, "Unsupported graphql selection node: #{ast_selection.class} (#{ast_selection.inspect})"
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
private
|
|
201
|
+
|
|
202
|
+
def propagate_errors(data, query)
|
|
203
|
+
paths_to_check = query.context.errors.map(&:path)
|
|
204
|
+
paths_to_check.compact! # root-level auth errors currently come without a path
|
|
205
|
+
# TODO dry with above?
|
|
206
|
+
# This is also where a query-level "Step" would be used?
|
|
207
|
+
selected_operation = query.document.definitions.first # TODO pick a selected operation
|
|
208
|
+
root_type = case selected_operation.operation_type
|
|
209
|
+
when nil, "query"
|
|
210
|
+
query.schema.query
|
|
211
|
+
when "mutation"
|
|
212
|
+
query.schema.mutation
|
|
213
|
+
when "subscription"
|
|
214
|
+
raise "Not implemented yet, TODO"
|
|
215
|
+
end
|
|
216
|
+
check_object_result(query, data, root_type, selected_operation.selections, [], [], paths_to_check)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def check_object_result(query, result_h, static_type, ast_selections, current_exec_path, current_result_path, paths_to_check)
|
|
220
|
+
current_path_len = current_exec_path.length
|
|
221
|
+
ast_selections.each do |ast_selection|
|
|
222
|
+
case ast_selection
|
|
223
|
+
when Language::Nodes::Field
|
|
224
|
+
begin
|
|
225
|
+
key = ast_selection.alias || ast_selection.name
|
|
226
|
+
current_exec_path << key
|
|
227
|
+
current_result_path << key
|
|
228
|
+
if paths_to_check.any? { |path_to_check| path_to_check[current_path_len] == key }
|
|
229
|
+
result_value = result_h[key]
|
|
230
|
+
field_defn = query.context.types.field(static_type, ast_selection.name)
|
|
231
|
+
result_type = field_defn.type
|
|
232
|
+
if (result_type_non_null = result_type.non_null?)
|
|
233
|
+
result_type = result_type.of_type
|
|
234
|
+
end
|
|
235
|
+
new_result_value = if result_value.is_a?(GraphQL::Error)
|
|
236
|
+
result_value.path = current_result_path.dup
|
|
237
|
+
nil
|
|
238
|
+
else
|
|
239
|
+
if result_type.list?
|
|
240
|
+
check_list_result(query, result_value, result_type.of_type, ast_selection.selections, current_exec_path, current_result_path, paths_to_check)
|
|
241
|
+
elsif result_type.kind.leaf?
|
|
242
|
+
result_value
|
|
243
|
+
else
|
|
244
|
+
check_object_result(query, result_value, result_type, ast_selection.selections, current_exec_path, current_result_path, paths_to_check)
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
if new_result_value.nil? && result_type_non_null
|
|
249
|
+
return nil
|
|
250
|
+
else
|
|
251
|
+
result_h[key] = new_result_value
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
ensure
|
|
255
|
+
current_exec_path.pop
|
|
256
|
+
current_result_path.pop
|
|
257
|
+
end
|
|
258
|
+
when Language::Nodes::InlineFragment
|
|
259
|
+
static_type_at_result = @static_types_at_result[result_h]
|
|
260
|
+
if static_type_at_result && type_condition_applies?(query.context, static_type_at_result, ast_selection.type.name)
|
|
261
|
+
result_h = check_object_result(query, result_h, static_type, ast_selection.selections, current_exec_path, current_result_path, paths_to_check)
|
|
262
|
+
end
|
|
263
|
+
when Language::Nodes::FragmentSpread
|
|
264
|
+
fragment_defn = query.document.definitions.find { |defn| defn.is_a?(Language::Nodes::FragmentDefinition) && defn.name == ast_selection.name }
|
|
265
|
+
static_type_at_result = @static_types_at_result[result_h]
|
|
266
|
+
if static_type_at_result && type_condition_applies?(query.context, static_type_at_result, fragment_defn.type.name)
|
|
267
|
+
result_h = check_object_result(query, result_h, static_type, fragment_defn.selections, current_exec_path, current_result_path, paths_to_check)
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
result_h
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def check_list_result(query, result_arr, inner_type, ast_selections, current_exec_path, current_result_path, paths_to_check)
|
|
276
|
+
inner_type_non_null = false
|
|
277
|
+
if inner_type.non_null?
|
|
278
|
+
inner_type_non_null = true
|
|
279
|
+
inner_type = inner_type.of_type
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
new_invalid_null = false
|
|
283
|
+
result_arr.map!.with_index do |result_item, idx|
|
|
284
|
+
current_result_path << idx
|
|
285
|
+
new_result = if result_item.is_a?(GraphQL::Error)
|
|
286
|
+
result_item.path = current_result_path.dup
|
|
287
|
+
nil
|
|
288
|
+
elsif inner_type.list?
|
|
289
|
+
check_list_result(query, result_item, inner_type.of_type, ast_selections, current_exec_path, current_result_path, paths_to_check)
|
|
290
|
+
elsif inner_type.kind.leaf?
|
|
291
|
+
result_item
|
|
292
|
+
else
|
|
293
|
+
check_object_result(query, result_item, inner_type, ast_selections, current_exec_path, current_result_path, paths_to_check)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
if new_result.nil? && inner_type_non_null
|
|
297
|
+
new_invalid_null = true
|
|
298
|
+
nil
|
|
299
|
+
else
|
|
300
|
+
new_result
|
|
301
|
+
end
|
|
302
|
+
ensure
|
|
303
|
+
current_result_path.pop
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
if new_invalid_null
|
|
307
|
+
nil
|
|
308
|
+
else
|
|
309
|
+
result_arr
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def dir_arg_value(query, arg_node)
|
|
314
|
+
if arg_node.value.is_a?(Language::Nodes::VariableIdentifier)
|
|
315
|
+
var_key = arg_node.value.name
|
|
316
|
+
if query.variables.key?(var_key)
|
|
317
|
+
query.variables[var_key]
|
|
318
|
+
else
|
|
319
|
+
query.variables[var_key.to_sym]
|
|
320
|
+
end
|
|
321
|
+
else
|
|
322
|
+
arg_node.value
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
def directives_include?(query, ast_selection)
|
|
326
|
+
if ast_selection.directives.any? { |dir_node|
|
|
327
|
+
if dir_node.name == "skip"
|
|
328
|
+
dir_node.arguments.any? { |arg_node| arg_node.name == "if" && dir_arg_value(query, arg_node) == true } # rubocop:disable Development/ContextIsPassedCop
|
|
329
|
+
elsif dir_node.name == "include"
|
|
330
|
+
dir_node.arguments.any? { |arg_node| arg_node.name == "if" && dir_arg_value(query, arg_node) == false } # rubocop:disable Development/ContextIsPassedCop
|
|
331
|
+
end
|
|
332
|
+
}
|
|
333
|
+
false
|
|
334
|
+
else
|
|
335
|
+
true
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def type_condition_applies?(context, concrete_type, type_name)
|
|
340
|
+
if type_name == concrete_type.graphql_name
|
|
341
|
+
true
|
|
342
|
+
else
|
|
343
|
+
abs_t = @schema.get_type(type_name, context)
|
|
344
|
+
p_types = @schema.possible_types(abs_t, context)
|
|
345
|
+
c_p_types = @schema.possible_types(concrete_type, context)
|
|
346
|
+
p_types.any? { |t| c_p_types.include?(t) }
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphQL
|
|
3
|
+
module Execution
|
|
4
|
+
module Batching
|
|
5
|
+
class SelectionsStep
|
|
6
|
+
def initialize(parent_type:, selections:, objects:, results:, runner:, query:, path:)
|
|
7
|
+
@path = path
|
|
8
|
+
@parent_type = parent_type
|
|
9
|
+
@selections = selections
|
|
10
|
+
@runner = runner
|
|
11
|
+
@objects = objects
|
|
12
|
+
@results = results
|
|
13
|
+
@query = query
|
|
14
|
+
@graphql_objects = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_reader :path, :query, :objects, :results
|
|
18
|
+
|
|
19
|
+
def graphql_objects
|
|
20
|
+
@graphql_objects ||= @objects.map do |obj|
|
|
21
|
+
@parent_type.scoped_new(obj, @query.context)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def call
|
|
26
|
+
grouped_selections = {}
|
|
27
|
+
prototype_result = @results.first
|
|
28
|
+
@runner.gather_selections(@parent_type, @selections, self, self.query, prototype_result, into: grouped_selections)
|
|
29
|
+
@results.each { |r| r.replace(prototype_result) }
|
|
30
|
+
grouped_selections.each_value do |frs|
|
|
31
|
+
@runner.add_step(frs)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "graphql/execution/batching/prepare_object_step"
|
|
3
|
+
require "graphql/execution/batching/field_compatibility"
|
|
4
|
+
require "graphql/execution/batching/field_resolve_step"
|
|
5
|
+
require "graphql/execution/batching/runner"
|
|
6
|
+
require "graphql/execution/batching/selections_step"
|
|
7
|
+
module GraphQL
|
|
8
|
+
module Execution
|
|
9
|
+
module Batching
|
|
10
|
+
module SchemaExtension
|
|
11
|
+
def execute_batching(query_str = nil, context: nil, document: nil, variables: nil, root_value: nil, validate: true, visibility_profile: nil)
|
|
12
|
+
multiplex_context = if context
|
|
13
|
+
{
|
|
14
|
+
backtrace: context[:backtrace],
|
|
15
|
+
tracers: context[:tracers],
|
|
16
|
+
trace: context[:trace],
|
|
17
|
+
dataloader: context[:dataloader],
|
|
18
|
+
trace_mode: context[:trace_mode],
|
|
19
|
+
}
|
|
20
|
+
else
|
|
21
|
+
{}
|
|
22
|
+
end
|
|
23
|
+
query_opts = {
|
|
24
|
+
query: query_str,
|
|
25
|
+
document: document,
|
|
26
|
+
context: context,
|
|
27
|
+
validate: validate,
|
|
28
|
+
variables: variables,
|
|
29
|
+
root_value: root_value,
|
|
30
|
+
visibility_profile: visibility_profile,
|
|
31
|
+
}
|
|
32
|
+
m_results = multiplex_batching([query_opts], context: multiplex_context, max_complexity: nil)
|
|
33
|
+
m_results[0]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def multiplex_batching(query_options, context: {}, max_complexity: self.max_complexity)
|
|
37
|
+
Batching.run_all(self, query_options, context: context, max_complexity: max_complexity)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.use(schema)
|
|
42
|
+
schema.extend(SchemaExtension)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
|
|
46
|
+
queries = query_options.map do |opts|
|
|
47
|
+
case opts
|
|
48
|
+
when Hash
|
|
49
|
+
schema.query_class.new(schema, nil, **opts)
|
|
50
|
+
when GraphQL::Query, GraphQL::Query::Partial
|
|
51
|
+
opts
|
|
52
|
+
else
|
|
53
|
+
raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
|
|
57
|
+
runner = Runner.new(multiplex)
|
|
58
|
+
runner.execute
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -3,9 +3,17 @@ module GraphQL
|
|
|
3
3
|
# If a field's resolve function returns a {ExecutionError},
|
|
4
4
|
# the error will be inserted into the response's `"errors"` key
|
|
5
5
|
# and the field will resolve to `nil`.
|
|
6
|
-
class ExecutionError < GraphQL::
|
|
6
|
+
class ExecutionError < GraphQL::RuntimeError
|
|
7
7
|
# @return [GraphQL::Language::Nodes::Field] the field where the error occurred
|
|
8
|
-
|
|
8
|
+
def ast_node
|
|
9
|
+
ast_nodes.first
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def ast_node=(new_node)
|
|
13
|
+
@ast_nodes = [new_node]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_accessor :ast_nodes
|
|
9
17
|
|
|
10
18
|
# @return [String] an array describing the JSON-path into the execution
|
|
11
19
|
# response which corresponds to this error.
|
|
@@ -21,8 +29,8 @@ module GraphQL
|
|
|
21
29
|
# under the `extensions` key.
|
|
22
30
|
attr_accessor :extensions
|
|
23
31
|
|
|
24
|
-
def initialize(message, ast_node: nil, options: nil, extensions: nil)
|
|
25
|
-
@
|
|
32
|
+
def initialize(message, ast_node: nil, ast_nodes: nil, options: nil, extensions: nil)
|
|
33
|
+
@ast_nodes = ast_nodes || [ast_node]
|
|
26
34
|
@options = options
|
|
27
35
|
@extensions = extensions
|
|
28
36
|
super(message)
|
|
@@ -34,12 +42,7 @@ module GraphQL
|
|
|
34
42
|
"message" => message,
|
|
35
43
|
}
|
|
36
44
|
if ast_node
|
|
37
|
-
hash["locations"] =
|
|
38
|
-
{
|
|
39
|
-
"line" => ast_node.line,
|
|
40
|
-
"column" => ast_node.col,
|
|
41
|
-
}
|
|
42
|
-
]
|
|
45
|
+
hash["locations"] = @ast_nodes.map { |a| { "line" => a.line, "column" => a.col } }
|
|
43
46
|
end
|
|
44
47
|
if path
|
|
45
48
|
hash["path"] = path
|
|
@@ -12,7 +12,7 @@ module GraphQL
|
|
|
12
12
|
field :name, String, null: false, method: :graphql_name
|
|
13
13
|
field :description, String
|
|
14
14
|
field :locations, [GraphQL::Schema::LateBoundType.new("__DirectiveLocation")], null: false, scope: false
|
|
15
|
-
field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false do
|
|
15
|
+
field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false, resolve_each: :resolve_args do
|
|
16
16
|
argument :include_deprecated, Boolean, required: false, default_value: false
|
|
17
17
|
end
|
|
18
18
|
field :on_operation, Boolean, null: false, deprecation_reason: "Use `locations`.", method: :on_operation?
|
|
@@ -21,11 +21,15 @@ module GraphQL
|
|
|
21
21
|
|
|
22
22
|
field :is_repeatable, Boolean, method: :repeatable?
|
|
23
23
|
|
|
24
|
-
def
|
|
25
|
-
args =
|
|
24
|
+
def self.resolve_args(object, context, include_deprecated:)
|
|
25
|
+
args = context.types.arguments(object)
|
|
26
26
|
args = args.reject(&:deprecation_reason) unless include_deprecated
|
|
27
27
|
args
|
|
28
28
|
end
|
|
29
|
+
|
|
30
|
+
def args(include_deprecated:)
|
|
31
|
+
self.class.resolve_args(object, context, include_deprecated: include_deprecated)
|
|
32
|
+
end
|
|
29
33
|
end
|
|
30
34
|
end
|
|
31
35
|
end
|
|
@@ -2,18 +2,22 @@
|
|
|
2
2
|
module GraphQL
|
|
3
3
|
module Introspection
|
|
4
4
|
class EntryPoints < Introspection::BaseObject
|
|
5
|
-
field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false, dynamic_introspection: true
|
|
5
|
+
field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false, dynamic_introspection: true, resolve_static: :__schema
|
|
6
6
|
field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system", dynamic_introspection: true do
|
|
7
7
|
argument :name, String
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
-
def __schema
|
|
10
|
+
def self.__schema(context)
|
|
11
11
|
# Apply wrapping manually since this field isn't wrapped by instrumentation
|
|
12
12
|
schema = context.schema
|
|
13
13
|
schema_type = schema.introspection_system.types["__Schema"]
|
|
14
14
|
schema_type.wrap(schema, context)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
def __schema
|
|
18
|
+
self.class.__schema(context)
|
|
19
|
+
end
|
|
20
|
+
|
|
17
21
|
def __type(name:)
|
|
18
22
|
if context.types.reachable_type?(name) && (type = context.types.type(name))
|
|
19
23
|
type
|
|
@@ -6,17 +6,17 @@ module GraphQL
|
|
|
6
6
|
description "One possible value for a given Enum. Enum values are unique values, not a "\
|
|
7
7
|
"placeholder for a string or numeric value. However an Enum value is returned in "\
|
|
8
8
|
"a JSON response as a string."
|
|
9
|
-
field :name, String, null: false
|
|
9
|
+
field :name, String, null: false, method: :graphql_name
|
|
10
10
|
field :description, String
|
|
11
|
-
field :is_deprecated, Boolean, null: false
|
|
11
|
+
field :is_deprecated, Boolean, null: false, resolve_each: :resolve_is_deprecated
|
|
12
12
|
field :deprecation_reason, String
|
|
13
13
|
|
|
14
|
-
def
|
|
15
|
-
object.
|
|
14
|
+
def self.resolve_is_deprecated(object, context)
|
|
15
|
+
!!object.deprecation_reason
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def is_deprecated
|
|
19
|
-
|
|
19
|
+
self.class.resolve_is_deprecated(object, context)
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -7,22 +7,30 @@ module GraphQL
|
|
|
7
7
|
"a name, potentially a list of arguments, and a return type."
|
|
8
8
|
field :name, String, null: false
|
|
9
9
|
field :description, String
|
|
10
|
-
field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false do
|
|
10
|
+
field :args, [GraphQL::Schema::LateBoundType.new("__InputValue")], null: false, scope: false, resolve_each: :resolve_args do
|
|
11
11
|
argument :include_deprecated, Boolean, required: false, default_value: false
|
|
12
12
|
end
|
|
13
13
|
field :type, GraphQL::Schema::LateBoundType.new("__Type"), null: false
|
|
14
|
-
field :is_deprecated, Boolean, null: false
|
|
14
|
+
field :is_deprecated, Boolean, null: false, resolve_each: :resolve_is_deprecated
|
|
15
15
|
field :deprecation_reason, String
|
|
16
16
|
|
|
17
|
+
def self.resolve_is_deprecated(object, _context)
|
|
18
|
+
!!object.deprecation_reason
|
|
19
|
+
end
|
|
20
|
+
|
|
17
21
|
def is_deprecated
|
|
18
|
-
|
|
22
|
+
self.class.resolve_is_deprecated(object, context)
|
|
19
23
|
end
|
|
20
24
|
|
|
21
|
-
def
|
|
22
|
-
args =
|
|
25
|
+
def self.resolve_args(object, context, include_deprecated:)
|
|
26
|
+
args = context.types.arguments(object)
|
|
23
27
|
args = args.reject(&:deprecation_reason) unless include_deprecated
|
|
24
28
|
args
|
|
25
29
|
end
|
|
30
|
+
|
|
31
|
+
def args(include_deprecated:)
|
|
32
|
+
self.class.resolve_args(object, context, include_deprecated: include_deprecated)
|
|
33
|
+
end
|
|
26
34
|
end
|
|
27
35
|
end
|
|
28
36
|
end
|