graphql 2.3.4 → 2.3.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
- data/lib/graphql/analysis/analyzer.rb +89 -0
- data/lib/graphql/analysis/field_usage.rb +82 -0
- data/lib/graphql/analysis/max_query_complexity.rb +20 -0
- data/lib/graphql/analysis/max_query_depth.rb +20 -0
- data/lib/graphql/analysis/query_complexity.rb +183 -0
- data/lib/graphql/analysis/{ast/query_depth.rb → query_depth.rb} +23 -25
- data/lib/graphql/analysis/visitor.rb +282 -0
- data/lib/graphql/analysis.rb +92 -1
- data/lib/graphql/dataloader/async_dataloader.rb +2 -0
- data/lib/graphql/dataloader/null_dataloader.rb +1 -1
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +7 -4
- data/lib/graphql/execution/interpreter/runtime.rb +40 -59
- data/lib/graphql/execution/interpreter.rb +2 -2
- data/lib/graphql/language/nodes.rb +17 -22
- data/lib/graphql/language/parser.rb +54 -13
- data/lib/graphql/query/validation_pipeline.rb +2 -2
- data/lib/graphql/query.rb +1 -1
- data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
- data/lib/graphql/schema/addition.rb +21 -11
- data/lib/graphql/schema/argument.rb +19 -5
- data/lib/graphql/schema/directive.rb +2 -0
- data/lib/graphql/schema/field.rb +8 -0
- data/lib/graphql/schema/has_single_input_argument.rb +1 -0
- data/lib/graphql/schema/input_object.rb +1 -0
- data/lib/graphql/schema/introspection_system.rb +2 -2
- data/lib/graphql/schema/late_bound_type.rb +4 -0
- data/lib/graphql/schema/list.rb +2 -2
- data/lib/graphql/schema/member/has_arguments.rb +2 -35
- data/lib/graphql/schema/member/has_directives.rb +1 -1
- data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
- data/lib/graphql/schema/member/type_system_helpers.rb +1 -2
- data/lib/graphql/schema/resolver.rb +1 -0
- data/lib/graphql/schema/warden.rb +2 -3
- data/lib/graphql/schema.rb +20 -20
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +1 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +1 -1
- data/lib/graphql/subscriptions.rb +1 -1
- data/lib/graphql/type_kinds.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +0 -8
- metadata +10 -11
- data/lib/graphql/analysis/ast/analyzer.rb +0 -91
- data/lib/graphql/analysis/ast/field_usage.rb +0 -84
- data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
- data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
- data/lib/graphql/analysis/ast/query_complexity.rb +0 -185
- data/lib/graphql/analysis/ast/visitor.rb +0 -284
- data/lib/graphql/analysis/ast.rb +0 -94
@@ -65,16 +65,6 @@ module GraphQL
|
|
65
65
|
"#<#{self.class.name} response=#{@response.inspect}>"
|
66
66
|
end
|
67
67
|
|
68
|
-
def tap_or_each(obj_or_array)
|
69
|
-
if obj_or_array.is_a?(Array)
|
70
|
-
obj_or_array.each do |item|
|
71
|
-
yield(item, true)
|
72
|
-
end
|
73
|
-
else
|
74
|
-
yield(obj_or_array, false)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
68
|
# This _begins_ the execution. Some deferred work
|
79
69
|
# might be stored up in lazies.
|
80
70
|
# @return [void]
|
@@ -84,7 +74,8 @@ module GraphQL
|
|
84
74
|
root_type = schema.root_type_for_operation(root_op_type)
|
85
75
|
runtime_object = root_type.wrap(query.root_value, context)
|
86
76
|
runtime_object = schema.sync_lazy(runtime_object)
|
87
|
-
|
77
|
+
is_eager = root_op_type == "mutation"
|
78
|
+
@response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, root_operation.selections, is_eager)
|
88
79
|
st = get_current_runtime_state
|
89
80
|
st.current_result = @response
|
90
81
|
|
@@ -93,17 +84,9 @@ module GraphQL
|
|
93
84
|
@response = nil
|
94
85
|
else
|
95
86
|
call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
|
96
|
-
|
97
|
-
# This is kind of a hack -- `gathered_selections` is an Array if any of the selections
|
98
|
-
# require isolation during execution (because of runtime directives). In that case,
|
99
|
-
# make a new, isolated result hash for writing the result into. (That isolated response
|
100
|
-
# is eventually merged back into the main response)
|
101
|
-
#
|
102
|
-
# Otherwise, `gathered_selections` is a hash of selections which can be
|
103
|
-
# directly evaluated and the results can be written right into the main response hash.
|
104
|
-
tap_or_each(gathered_selections) do |selections, is_selection_array|
|
87
|
+
each_gathered_selections(@response) do |selections, is_selection_array|
|
105
88
|
if is_selection_array
|
106
|
-
selection_response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false)
|
89
|
+
selection_response = GraphQLResultHash.new(nil, root_type, runtime_object, nil, false, selections, is_eager)
|
107
90
|
final_response = @response
|
108
91
|
else
|
109
92
|
selection_response = @response
|
@@ -112,12 +95,10 @@ module GraphQL
|
|
112
95
|
|
113
96
|
@dataloader.append_job {
|
114
97
|
evaluate_selections(
|
115
|
-
root_op_type == "mutation",
|
116
98
|
selections,
|
117
99
|
selection_response,
|
118
100
|
final_response,
|
119
101
|
nil,
|
120
|
-
nil,
|
121
102
|
)
|
122
103
|
}
|
123
104
|
end
|
@@ -126,6 +107,17 @@ module GraphQL
|
|
126
107
|
nil
|
127
108
|
end
|
128
109
|
|
110
|
+
def each_gathered_selections(response_hash)
|
111
|
+
gathered_selections = gather_selections(response_hash.graphql_application_value, response_hash.graphql_result_type, response_hash.graphql_selections)
|
112
|
+
if gathered_selections.is_a?(Array)
|
113
|
+
gathered_selections.each do |item|
|
114
|
+
yield(item, true)
|
115
|
+
end
|
116
|
+
else
|
117
|
+
yield(gathered_selections, false)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
129
121
|
def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = {})
|
130
122
|
selections.each do |node|
|
131
123
|
# Skip gathering this if the directive says so
|
@@ -138,7 +130,7 @@ module GraphQL
|
|
138
130
|
selections = selections_by_name[response_key]
|
139
131
|
# if there was already a selection of this field,
|
140
132
|
# use an array to hold all selections,
|
141
|
-
#
|
133
|
+
# otherwise, use the single node to represent the selection
|
142
134
|
if selections
|
143
135
|
# This field was already selected at least once,
|
144
136
|
# add this node to the list of selections
|
@@ -203,7 +195,7 @@ module GraphQL
|
|
203
195
|
NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
|
204
196
|
|
205
197
|
# @return [void]
|
206
|
-
def evaluate_selections(
|
198
|
+
def evaluate_selections(gathered_selections, selections_result, target_result, runtime_state) # rubocop:disable Metrics/ParameterLists
|
207
199
|
runtime_state ||= get_current_runtime_state
|
208
200
|
runtime_state.current_result_name = nil
|
209
201
|
runtime_state.current_result = selections_result
|
@@ -218,7 +210,7 @@ module GraphQL
|
|
218
210
|
gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
|
219
211
|
@dataloader.append_job {
|
220
212
|
evaluate_selection(
|
221
|
-
result_name, field_ast_nodes_or_ast_node,
|
213
|
+
result_name, field_ast_nodes_or_ast_node, selections_result
|
222
214
|
)
|
223
215
|
finished_jobs += 1
|
224
216
|
if target_result && finished_jobs == enqueued_jobs
|
@@ -228,7 +220,7 @@ module GraphQL
|
|
228
220
|
# Field resolution may pause the fiber,
|
229
221
|
# so it wouldn't get to the `Resolve` call that happens below.
|
230
222
|
# So instead trigger a run from this outer context.
|
231
|
-
if
|
223
|
+
if selections_result.graphql_is_eager
|
232
224
|
@dataloader.clear_cache
|
233
225
|
@dataloader.run
|
234
226
|
@dataloader.clear_cache
|
@@ -239,7 +231,7 @@ module GraphQL
|
|
239
231
|
end
|
240
232
|
|
241
233
|
# @return [void]
|
242
|
-
def evaluate_selection(result_name, field_ast_nodes_or_ast_node,
|
234
|
+
def evaluate_selection(result_name, field_ast_nodes_or_ast_node, selections_result) # rubocop:disable Metrics/ParameterLists
|
243
235
|
return if selections_result.graphql_dead
|
244
236
|
# As a performance optimization, the hash key will be a `Node` if
|
245
237
|
# there's only one selection of the field. But if there are multiple
|
@@ -266,28 +258,27 @@ module GraphQL
|
|
266
258
|
owner_object = field_defn.owner.wrap(owner_object, context)
|
267
259
|
end
|
268
260
|
|
269
|
-
return_type = field_defn.type
|
270
261
|
if !field_defn.any_arguments?
|
271
262
|
resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
|
272
263
|
if field_defn.extras.size == 0
|
273
264
|
evaluate_selection_with_resolved_keyword_args(
|
274
|
-
NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object,
|
265
|
+
NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state
|
275
266
|
)
|
276
267
|
else
|
277
|
-
evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object,
|
268
|
+
evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state)
|
278
269
|
end
|
279
270
|
else
|
280
271
|
@query.arguments_cache.dataload_for(ast_node, field_defn, owner_object) do |resolved_arguments|
|
281
272
|
runtime_state = get_current_runtime_state # This might be in a different fiber
|
282
|
-
evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object,
|
273
|
+
evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state)
|
283
274
|
end
|
284
275
|
end
|
285
276
|
end
|
286
277
|
|
287
|
-
def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, object,
|
278
|
+
def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state) # rubocop:disable Metrics/ParameterLists
|
288
279
|
after_lazy(arguments, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |resolved_arguments, runtime_state|
|
289
|
-
return_type_non_null = return_type.non_null?
|
290
280
|
if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
|
281
|
+
return_type_non_null = field_defn.type.non_null?
|
291
282
|
continue_value(resolved_arguments, field_defn, return_type_non_null, ast_node, result_name, selection_result)
|
292
283
|
next
|
293
284
|
end
|
@@ -326,7 +317,8 @@ module GraphQL
|
|
326
317
|
# to the keyword args hash _before_ freezing everything.
|
327
318
|
extra_args[:argument_details] = :__arguments_add_self
|
328
319
|
when :parent
|
329
|
-
|
320
|
+
parent_result = selection_result.graphql_parent
|
321
|
+
extra_args[:parent] = parent_result&.graphql_application_value&.object
|
330
322
|
else
|
331
323
|
extra_args[extra] = field_defn.fetch_extra(extra, context)
|
332
324
|
end
|
@@ -337,11 +329,11 @@ module GraphQL
|
|
337
329
|
resolved_arguments.keyword_arguments
|
338
330
|
end
|
339
331
|
|
340
|
-
evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, object,
|
332
|
+
evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state)
|
341
333
|
end
|
342
334
|
end
|
343
335
|
|
344
|
-
def evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, object,
|
336
|
+
def evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state) # rubocop:disable Metrics/ParameterLists
|
345
337
|
runtime_state.current_field = field_defn
|
346
338
|
runtime_state.current_arguments = resolved_arguments
|
347
339
|
runtime_state.current_result_name = result_name
|
@@ -384,7 +376,8 @@ module GraphQL
|
|
384
376
|
end
|
385
377
|
after_lazy(app_result, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_result, runtime_state|
|
386
378
|
owner_type = selection_result.graphql_result_type
|
387
|
-
|
379
|
+
return_type = field_defn.type
|
380
|
+
continue_value = continue_value(inner_result, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
|
388
381
|
if HALT != continue_value
|
389
382
|
was_scoped = runtime_state.was_authorized_by_scope_items
|
390
383
|
runtime_state.was_authorized_by_scope_items = nil
|
@@ -395,7 +388,7 @@ module GraphQL
|
|
395
388
|
# If this field is a root mutation field, immediately resolve
|
396
389
|
# all of its child fields before moving on to the next root mutation field.
|
397
390
|
# (Subselections of this mutation will still be resolved level-by-level.)
|
398
|
-
if
|
391
|
+
if selection_result.graphql_is_eager
|
399
392
|
Interpreter::Resolve.resolve_all([field_result], @dataloader)
|
400
393
|
end
|
401
394
|
end
|
@@ -607,21 +600,11 @@ module GraphQL
|
|
607
600
|
after_lazy(object_proxy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_object, runtime_state|
|
608
601
|
continue_value = continue_value(inner_object, field, is_non_null, ast_node, result_name, selection_result)
|
609
602
|
if HALT != continue_value
|
610
|
-
response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null)
|
603
|
+
response_hash = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, next_selections, false)
|
611
604
|
set_result(selection_result, result_name, response_hash, true, is_non_null)
|
612
|
-
|
613
|
-
gathered_selections = gather_selections(continue_value, current_type, next_selections)
|
614
|
-
# There are two possibilities for `gathered_selections`:
|
615
|
-
# 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
|
616
|
-
# This case is handled below, and the result can be written right into the main `response_hash` above.
|
617
|
-
# In this case, `gathered_selections` is a hash of selections.
|
618
|
-
# 2. Some selections of this object have runtime directives that may or may not modify execution.
|
619
|
-
# That part of the selection is evaluated in an isolated way, writing into a sub-response object which is
|
620
|
-
# eventually merged into the final response. In this case, `gathered_selections` is an array of things to run in isolation.
|
621
|
-
# (Technically, it's possible that one of those entries _doesn't_ require isolation.)
|
622
|
-
tap_or_each(gathered_selections) do |selections, is_selection_array|
|
605
|
+
each_gathered_selections(response_hash) do |selections, is_selection_array|
|
623
606
|
if is_selection_array
|
624
|
-
this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null)
|
607
|
+
this_result = GraphQLResultHash.new(result_name, current_type, continue_value, selection_result, is_non_null, selections, false)
|
625
608
|
final_result = response_hash
|
626
609
|
else
|
627
610
|
this_result = response_hash
|
@@ -629,11 +612,9 @@ module GraphQL
|
|
629
612
|
end
|
630
613
|
|
631
614
|
evaluate_selections(
|
632
|
-
false,
|
633
615
|
selections,
|
634
616
|
this_result,
|
635
617
|
final_result,
|
636
|
-
owner_object.object,
|
637
618
|
runtime_state,
|
638
619
|
)
|
639
620
|
end
|
@@ -644,7 +625,7 @@ module GraphQL
|
|
644
625
|
# This is true for objects, unions, and interfaces
|
645
626
|
use_dataloader_job = !inner_type.unwrap.kind.input?
|
646
627
|
inner_type_non_null = inner_type.non_null?
|
647
|
-
response_list = GraphQLResultArray.new(result_name, current_type,
|
628
|
+
response_list = GraphQLResultArray.new(result_name, current_type, owner_object, selection_result, is_non_null, next_selections, false)
|
648
629
|
set_result(selection_result, result_name, response_list, true, is_non_null)
|
649
630
|
idx = nil
|
650
631
|
list_value = begin
|
@@ -654,10 +635,10 @@ module GraphQL
|
|
654
635
|
idx += 1
|
655
636
|
if use_dataloader_job
|
656
637
|
@dataloader.append_job do
|
657
|
-
resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list,
|
638
|
+
resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state)
|
658
639
|
end
|
659
640
|
else
|
660
|
-
resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list,
|
641
|
+
resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state)
|
661
642
|
end
|
662
643
|
end
|
663
644
|
|
@@ -688,7 +669,7 @@ module GraphQL
|
|
688
669
|
end
|
689
670
|
end
|
690
671
|
|
691
|
-
def resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list,
|
672
|
+
def resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists
|
692
673
|
runtime_state.current_result_name = this_idx
|
693
674
|
runtime_state.current_result = response_list
|
694
675
|
call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
|
@@ -696,7 +677,7 @@ module GraphQL
|
|
696
677
|
after_lazy(inner_value, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list, runtime_state: runtime_state) do |inner_inner_value, runtime_state|
|
697
678
|
continue_value = continue_value(inner_inner_value, field, inner_type_non_null, ast_node, this_idx, response_list)
|
698
679
|
if HALT != continue_value
|
699
|
-
continue_field(continue_value, owner_type, field, inner_type, ast_node,
|
680
|
+
continue_field(continue_value, owner_type, field, inner_type, ast_node, response_list.graphql_selections, false, owner_object, arguments, this_idx, response_list, was_scoped, runtime_state)
|
700
681
|
end
|
701
682
|
end
|
702
683
|
end
|
@@ -20,7 +20,7 @@ module GraphQL
|
|
20
20
|
# @param queries [Array<GraphQL::Query, Hash>]
|
21
21
|
# @param context [Hash]
|
22
22
|
# @param max_complexity [Integer, nil]
|
23
|
-
# @return [Array<
|
23
|
+
# @return [Array<GraphQL::Query::Result>] One result per query
|
24
24
|
def run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
|
25
25
|
queries = query_options.map do |opts|
|
26
26
|
case opts
|
@@ -40,7 +40,7 @@ module GraphQL
|
|
40
40
|
lazies_at_depth = Hash.new { |h, k| h[k] = [] }
|
41
41
|
multiplex_analyzers = schema.multiplex_analyzers
|
42
42
|
if multiplex.max_complexity
|
43
|
-
multiplex_analyzers += [GraphQL::Analysis::
|
43
|
+
multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity]
|
44
44
|
end
|
45
45
|
|
46
46
|
schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers)
|
@@ -274,6 +274,8 @@ module GraphQL
|
|
274
274
|
]
|
275
275
|
|
276
276
|
def generate_initialize
|
277
|
+
return if method_defined?(:marshal_load, false) # checking for `:initialize` doesn't work right
|
278
|
+
|
277
279
|
scalar_method_names = @scalar_methods
|
278
280
|
# TODO: These probably should be scalar methods, but `types` returns an array
|
279
281
|
[:types, :description].each do |extra_method|
|
@@ -392,16 +394,6 @@ module GraphQL
|
|
392
394
|
|
393
395
|
# A single selection in a GraphQL query.
|
394
396
|
class Field < AbstractNode
|
395
|
-
scalar_methods :name, :alias
|
396
|
-
children_methods({
|
397
|
-
arguments: GraphQL::Language::Nodes::Argument,
|
398
|
-
selections: GraphQL::Language::Nodes::Field,
|
399
|
-
directives: GraphQL::Language::Nodes::Directive,
|
400
|
-
})
|
401
|
-
|
402
|
-
# @!attribute selections
|
403
|
-
# @return [Array<Nodes::Field>] Selections on this object (or empty array if this is a scalar field)
|
404
|
-
|
405
397
|
def initialize(name: nil, arguments: NONE, directives: NONE, selections: NONE, field_alias: nil, line: nil, col: nil, pos: nil, filename: nil, source: nil)
|
406
398
|
@name = name
|
407
399
|
@arguments = arguments || NONE
|
@@ -428,24 +420,19 @@ module GraphQL
|
|
428
420
|
@line, @col, @filename, @name, @arguments, @directives, @selections, @alias = values
|
429
421
|
end
|
430
422
|
|
431
|
-
|
432
|
-
self.children_method_name = :selections
|
433
|
-
end
|
434
|
-
|
435
|
-
# A reusable fragment, defined at document-level.
|
436
|
-
class FragmentDefinition < AbstractNode
|
437
|
-
scalar_methods :name, :type
|
423
|
+
scalar_methods :name, :alias
|
438
424
|
children_methods({
|
425
|
+
arguments: GraphQL::Language::Nodes::Argument,
|
439
426
|
selections: GraphQL::Language::Nodes::Field,
|
440
427
|
directives: GraphQL::Language::Nodes::Directive,
|
441
428
|
})
|
442
429
|
|
443
|
-
|
444
|
-
|
445
|
-
|
430
|
+
# Override this because default is `:fields`
|
431
|
+
self.children_method_name = :selections
|
432
|
+
end
|
446
433
|
|
447
|
-
|
448
|
-
|
434
|
+
# A reusable fragment, defined at document-level.
|
435
|
+
class FragmentDefinition < AbstractNode
|
449
436
|
def initialize(name: nil, type: nil, directives: NONE, selections: NONE, filename: nil, pos: nil, source: nil, line: nil, col: nil)
|
450
437
|
@name = name
|
451
438
|
@type = type
|
@@ -469,6 +456,14 @@ module GraphQL
|
|
469
456
|
def marshal_load(values)
|
470
457
|
@line, @col, @filename, @name, @type, @directives, @selections = values
|
471
458
|
end
|
459
|
+
|
460
|
+
scalar_methods :name, :type
|
461
|
+
children_methods({
|
462
|
+
selections: GraphQL::Language::Nodes::Field,
|
463
|
+
directives: GraphQL::Language::Nodes::Directive,
|
464
|
+
})
|
465
|
+
|
466
|
+
self.children_method_name = :definitions
|
472
467
|
end
|
473
468
|
|
474
469
|
# Application of a named fragment in a selection
|
@@ -379,7 +379,12 @@ module GraphQL
|
|
379
379
|
v_loc = pos
|
380
380
|
description = if at?(:STRING); string_value; end
|
381
381
|
defn_loc = pos
|
382
|
-
|
382
|
+
# Any identifier, but not true, false, or null
|
383
|
+
enum_value = if at?(:TRUE) || at?(:FALSE) || at?(:NULL)
|
384
|
+
expect_token(:IDENTIFIER)
|
385
|
+
else
|
386
|
+
parse_name
|
387
|
+
end
|
383
388
|
v_directives = parse_directives
|
384
389
|
list << EnumValueDefinition.new(pos: v_loc, definition_pos: defn_loc, description: description, name: enum_value, directives: v_directives, filename: @filename, source: self)
|
385
390
|
end
|
@@ -615,9 +620,6 @@ module GraphQL
|
|
615
620
|
when :ON
|
616
621
|
advance_token
|
617
622
|
"on"
|
618
|
-
when :DIRECTIVE
|
619
|
-
advance_token
|
620
|
-
"directive"
|
621
623
|
when :EXTEND
|
622
624
|
advance_token
|
623
625
|
"extend"
|
@@ -634,15 +636,6 @@ module GraphQL
|
|
634
636
|
end
|
635
637
|
end
|
636
638
|
|
637
|
-
# Any identifier, but not true, false, or null
|
638
|
-
def parse_enum_name
|
639
|
-
if at?(:TRUE) || at?(:FALSE) || at?(:NULL)
|
640
|
-
expect_token(:IDENTIFIER)
|
641
|
-
else
|
642
|
-
parse_name
|
643
|
-
end
|
644
|
-
end
|
645
|
-
|
646
639
|
def parse_type_name
|
647
640
|
TypeName.new(pos: pos, name: parse_name, filename: @filename, source: self)
|
648
641
|
end
|
@@ -733,6 +726,54 @@ module GraphQL
|
|
733
726
|
loc = pos
|
734
727
|
advance_token
|
735
728
|
VariableIdentifier.new(pos: loc, name: parse_name, filename: @filename, source: self)
|
729
|
+
when :SCHEMA
|
730
|
+
advance_token
|
731
|
+
Nodes::Enum.new(pos: pos, name: "schema", filename: @filename, source: self)
|
732
|
+
when :SCALAR
|
733
|
+
advance_token
|
734
|
+
Nodes::Enum.new(pos: pos, name: "scalar", filename: @filename, source: self)
|
735
|
+
when :IMPLEMENTS
|
736
|
+
advance_token
|
737
|
+
Nodes::Enum.new(pos: pos, name: "implements", filename: @filename, source: self)
|
738
|
+
when :INTERFACE
|
739
|
+
advance_token
|
740
|
+
Nodes::Enum.new(pos: pos, name: "interface", filename: @filename, source: self)
|
741
|
+
when :UNION
|
742
|
+
advance_token
|
743
|
+
Nodes::Enum.new(pos: pos, name: "union", filename: @filename, source: self)
|
744
|
+
when :ENUM
|
745
|
+
advance_token
|
746
|
+
Nodes::Enum.new(pos: pos, name: "enum", filename: @filename, source: self)
|
747
|
+
when :INPUT
|
748
|
+
advance_token
|
749
|
+
Nodes::Enum.new(pos: pos, name: "input", filename: @filename, source: self)
|
750
|
+
when :DIRECTIVE
|
751
|
+
advance_token
|
752
|
+
Nodes::Enum.new(pos: pos, name: "directive", filename: @filename, source: self)
|
753
|
+
when :TYPE
|
754
|
+
advance_token
|
755
|
+
Nodes::Enum.new(pos: pos, name: "type", filename: @filename, source: self)
|
756
|
+
when :QUERY
|
757
|
+
advance_token
|
758
|
+
Nodes::Enum.new(pos: pos, name: "query", filename: @filename, source: self)
|
759
|
+
when :MUTATION
|
760
|
+
advance_token
|
761
|
+
Nodes::Enum.new(pos: pos, name: "mutation", filename: @filename, source: self)
|
762
|
+
when :SUBSCRIPTION
|
763
|
+
advance_token
|
764
|
+
Nodes::Enum.new(pos: pos, name: "subscription", filename: @filename, source: self)
|
765
|
+
when :FRAGMENT
|
766
|
+
advance_token
|
767
|
+
Nodes::Enum.new(pos: pos, name: "fragment", filename: @filename, source: self)
|
768
|
+
when :REPEATABLE
|
769
|
+
advance_token
|
770
|
+
Nodes::Enum.new(pos: pos, name: "repeatable", filename: @filename, source: self)
|
771
|
+
when :ON
|
772
|
+
advance_token
|
773
|
+
Nodes::Enum.new(pos: pos, name: "on", filename: @filename, source: self)
|
774
|
+
when :EXTEND
|
775
|
+
advance_token
|
776
|
+
Nodes::Enum.new(pos: pos, name: "extend", filename: @filename, source: self)
|
736
777
|
else
|
737
778
|
expect_token(:VALUE)
|
738
779
|
end
|
@@ -100,10 +100,10 @@ module GraphQL
|
|
100
100
|
# Depending on the analysis engine, we must use different analyzers
|
101
101
|
# remove this once everything has switched over to AST analyzers
|
102
102
|
if max_depth
|
103
|
-
qa << GraphQL::Analysis::
|
103
|
+
qa << GraphQL::Analysis::MaxQueryDepth
|
104
104
|
end
|
105
105
|
if max_complexity
|
106
|
-
qa << GraphQL::Analysis::
|
106
|
+
qa << GraphQL::Analysis::MaxQueryComplexity
|
107
107
|
end
|
108
108
|
qa
|
109
109
|
else
|
data/lib/graphql/query.rb
CHANGED
@@ -222,7 +222,7 @@ module GraphQL
|
|
222
222
|
end
|
223
223
|
|
224
224
|
# Get the result for this query, executing it once
|
225
|
-
# @return [
|
225
|
+
# @return [GraphQL::Query::Result] A Hash-like GraphQL response, with `"data"` and/or `"errors"` keys
|
226
226
|
def result
|
227
227
|
if !@executed
|
228
228
|
Execution::Interpreter.run_all(@schema, [self], context: @context)
|
@@ -9,7 +9,7 @@ module GraphQL
|
|
9
9
|
|
10
10
|
# Return the source of `send_node`, but without the keyword argument represented by `pair_node`
|
11
11
|
def source_without_keyword_argument(send_node, pair_node)
|
12
|
-
# work back to the
|
12
|
+
# work back to the preceding comma
|
13
13
|
first_pos = pair_node.location.expression.begin_pos
|
14
14
|
end_pos = pair_node.location.expression.end_pos
|
15
15
|
node_source = send_node.source_range.source
|
@@ -12,7 +12,7 @@ module GraphQL
|
|
12
12
|
@possible_types = {}
|
13
13
|
@types = {}
|
14
14
|
@union_memberships = {}
|
15
|
-
@references = Hash.new { |h, k| h[k] =
|
15
|
+
@references = Hash.new { |h, k| h[k] = Set.new }
|
16
16
|
@arguments_with_default_values = []
|
17
17
|
add_type_and_traverse(new_types)
|
18
18
|
end
|
@@ -20,7 +20,7 @@ module GraphQL
|
|
20
20
|
private
|
21
21
|
|
22
22
|
def references_to(thing, from:)
|
23
|
-
@references[thing]
|
23
|
+
@references[thing].add(from)
|
24
24
|
end
|
25
25
|
|
26
26
|
def get_type(name)
|
@@ -95,7 +95,7 @@ module GraphQL
|
|
95
95
|
# It's a union with possible_types
|
96
96
|
# Replace the item by class name
|
97
97
|
owner.assign_type_membership_object_type(type)
|
98
|
-
@possible_types[owner
|
98
|
+
@possible_types[owner] = owner.possible_types
|
99
99
|
elsif type.kind.interface? && (owner.kind.object? || owner.kind.interface?)
|
100
100
|
new_interfaces = []
|
101
101
|
owner.interfaces.each do |int_t|
|
@@ -110,7 +110,7 @@ module GraphQL
|
|
110
110
|
end
|
111
111
|
owner.implements(*new_interfaces)
|
112
112
|
new_interfaces.each do |int|
|
113
|
-
pt = @possible_types[int
|
113
|
+
pt = @possible_types[int] ||= []
|
114
114
|
if !pt.include?(owner) && owner.is_a?(Class)
|
115
115
|
pt << owner
|
116
116
|
end
|
@@ -126,6 +126,7 @@ module GraphQL
|
|
126
126
|
@types[type.graphql_name] = type
|
127
127
|
when GraphQL::Schema::Field, GraphQL::Schema::Argument
|
128
128
|
orig_type = owner.type
|
129
|
+
unwrapped_t = type
|
129
130
|
# Apply list/non-null wrapper as needed
|
130
131
|
if orig_type.respond_to?(:of_type)
|
131
132
|
transforms = []
|
@@ -142,6 +143,7 @@ module GraphQL
|
|
142
143
|
transforms.reverse_each { |t| type = type.public_send(t) }
|
143
144
|
end
|
144
145
|
owner.type = type
|
146
|
+
references_to(unwrapped_t, from: owner)
|
145
147
|
else
|
146
148
|
raise "Unexpected update: #{owner.inspect} #{type.inspect}"
|
147
149
|
end
|
@@ -164,7 +166,9 @@ module GraphQL
|
|
164
166
|
@directives << type
|
165
167
|
type.all_argument_definitions.each do |arg|
|
166
168
|
arg_type = arg.type.unwrap
|
167
|
-
|
169
|
+
if !arg_type.is_a?(GraphQL::Schema::LateBoundType)
|
170
|
+
references_to(arg_type, from: arg)
|
171
|
+
end
|
168
172
|
path.push(arg.graphql_name)
|
169
173
|
add_type(arg_type, owner: arg, late_types: late_types, path: path)
|
170
174
|
path.pop
|
@@ -187,14 +191,18 @@ module GraphQL
|
|
187
191
|
type.all_field_definitions.each do |field|
|
188
192
|
name = field.graphql_name
|
189
193
|
field_type = field.type.unwrap
|
190
|
-
|
194
|
+
if !field_type.is_a?(GraphQL::Schema::LateBoundType)
|
195
|
+
references_to(field_type, from: field)
|
196
|
+
end
|
191
197
|
path.push(name)
|
192
198
|
add_type(field_type, owner: field, late_types: late_types, path: path)
|
193
199
|
add_directives_from(field)
|
194
200
|
field.all_argument_definitions.each do |arg|
|
195
201
|
add_directives_from(arg)
|
196
202
|
arg_type = arg.type.unwrap
|
197
|
-
|
203
|
+
if !arg_type.is_a?(GraphQL::Schema::LateBoundType)
|
204
|
+
references_to(arg_type, from: arg)
|
205
|
+
end
|
198
206
|
path.push(arg.graphql_name)
|
199
207
|
add_type(arg_type, owner: arg, late_types: late_types, path: path)
|
200
208
|
path.pop
|
@@ -209,7 +217,9 @@ module GraphQL
|
|
209
217
|
type.all_argument_definitions.each do |arg|
|
210
218
|
add_directives_from(arg)
|
211
219
|
arg_type = arg.type.unwrap
|
212
|
-
|
220
|
+
if !arg_type.is_a?(GraphQL::Schema::LateBoundType)
|
221
|
+
references_to(arg_type, from: arg)
|
222
|
+
end
|
213
223
|
path.push(arg.graphql_name)
|
214
224
|
add_type(arg_type, owner: arg, late_types: late_types, path: path)
|
215
225
|
path.pop
|
@@ -219,7 +229,7 @@ module GraphQL
|
|
219
229
|
end
|
220
230
|
end
|
221
231
|
if type.kind.union?
|
222
|
-
@possible_types[type
|
232
|
+
@possible_types[type] = type.all_possible_types
|
223
233
|
path.push("possible_types")
|
224
234
|
type.all_possible_types.each do |t|
|
225
235
|
add_type(t, owner: type, late_types: late_types, path: path)
|
@@ -234,7 +244,7 @@ module GraphQL
|
|
234
244
|
path.pop
|
235
245
|
end
|
236
246
|
if type.kind.object?
|
237
|
-
possible_types_for_this_name = @possible_types[type
|
247
|
+
possible_types_for_this_name = @possible_types[type] ||= []
|
238
248
|
possible_types_for_this_name << type
|
239
249
|
end
|
240
250
|
|
@@ -246,7 +256,7 @@ module GraphQL
|
|
246
256
|
interface_type = interface_type_membership.abstract_type
|
247
257
|
# We can get these now; we'll have to get late-bound types later
|
248
258
|
if interface_type.is_a?(Module) && type.is_a?(Class)
|
249
|
-
implementers = @possible_types[interface_type
|
259
|
+
implementers = @possible_types[interface_type] ||= []
|
250
260
|
implementers << type
|
251
261
|
end
|
252
262
|
when String, Schema::LateBoundType
|
@@ -312,10 +312,15 @@ module GraphQL
|
|
312
312
|
context.query.after_lazy(custom_loaded_value) do |custom_value|
|
313
313
|
if loads
|
314
314
|
if type.list?
|
315
|
-
loaded_values =
|
316
|
-
|
317
|
-
|
318
|
-
|
315
|
+
loaded_values = []
|
316
|
+
context.dataloader.run_isolated do
|
317
|
+
custom_value.each_with_index.map { |custom_val, idx|
|
318
|
+
id = coerced_value[idx]
|
319
|
+
context.dataloader.append_job do
|
320
|
+
loaded_values[idx] = load_method_owner.authorize_application_object(self, id, context, custom_val)
|
321
|
+
end
|
322
|
+
}
|
323
|
+
end
|
319
324
|
context.schema.after_any_lazies(loaded_values, &:itself)
|
320
325
|
else
|
321
326
|
load_method_owner.authorize_application_object(self, coerced_value, context, custom_loaded_value)
|
@@ -326,7 +331,16 @@ module GraphQL
|
|
326
331
|
end
|
327
332
|
elsif loads
|
328
333
|
if type.list?
|
329
|
-
loaded_values =
|
334
|
+
loaded_values = []
|
335
|
+
# We want to run these list items all together,
|
336
|
+
# but we also need to wait for the result so we can return it :S
|
337
|
+
context.dataloader.run_isolated do
|
338
|
+
coerced_value.each_with_index { |val, idx|
|
339
|
+
context.dataloader.append_job do
|
340
|
+
loaded_values[idx] = load_method_owner.load_and_authorize_application_object(self, val, context)
|
341
|
+
end
|
342
|
+
}
|
343
|
+
end
|
330
344
|
context.schema.after_any_lazies(loaded_values, &:itself)
|
331
345
|
else
|
332
346
|
load_method_owner.load_and_authorize_application_object(self, coerced_value, context)
|
@@ -188,6 +188,8 @@ module GraphQL
|
|
188
188
|
assert_has_location(SCALAR)
|
189
189
|
elsif @owner < GraphQL::Schema
|
190
190
|
assert_has_location(SCHEMA)
|
191
|
+
elsif @owner < GraphQL::Schema::Resolver
|
192
|
+
assert_has_location(FIELD_DEFINITION)
|
191
193
|
else
|
192
194
|
raise "Unexpected directive owner class: #{@owner}"
|
193
195
|
end
|