graphql 2.5.4 → 2.5.11
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/graphql/analysis/query_complexity.rb +2 -2
- data/lib/graphql/dataloader/active_record_association_source.rb +14 -2
- data/lib/graphql/dataloader/null_dataloader.rb +7 -0
- data/lib/graphql/execution/interpreter/resolve.rb +3 -3
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +11 -1
- data/lib/graphql/execution/interpreter/runtime.rb +136 -49
- data/lib/graphql/execution/interpreter.rb +1 -1
- data/lib/graphql/language/lexer.rb +9 -2
- data/lib/graphql/language/nodes.rb +5 -1
- data/lib/graphql/language/parser.rb +1 -0
- data/lib/graphql/query/context.rb +1 -1
- data/lib/graphql/query/partial.rb +179 -0
- data/lib/graphql/query.rb +55 -43
- data/lib/graphql/schema/addition.rb +3 -1
- data/lib/graphql/schema/argument.rb +5 -0
- data/lib/graphql/schema/build_from_definition.rb +5 -1
- data/lib/graphql/schema/directive.rb +33 -1
- data/lib/graphql/schema/field.rb +8 -0
- data/lib/graphql/schema/input_object.rb +28 -21
- data/lib/graphql/schema/member/has_arguments.rb +2 -2
- data/lib/graphql/schema/member/has_interfaces.rb +2 -2
- data/lib/graphql/schema/member/type_system_helpers.rb +16 -2
- data/lib/graphql/schema/ractor_shareable.rb +79 -0
- data/lib/graphql/schema/scalar.rb +1 -6
- data/lib/graphql/schema/timeout.rb +19 -2
- data/lib/graphql/schema/validator/required_validator.rb +15 -6
- data/lib/graphql/schema/visibility/migration.rb +2 -2
- data/lib/graphql/schema/visibility/profile.rb +74 -10
- data/lib/graphql/schema/visibility.rb +30 -17
- data/lib/graphql/schema/warden.rb +13 -5
- data/lib/graphql/schema.rb +53 -22
- data/lib/graphql/static_validation/all_rules.rb +1 -1
- data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
- data/lib/graphql/tracing/notifications_trace.rb +3 -1
- data/lib/graphql/tracing/null_trace.rb +1 -1
- data/lib/graphql/tracing/perfetto_trace.rb +1 -1
- data/lib/graphql/type_kinds.rb +1 -0
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +1 -1
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c2ef756861a779063236ee9f8745d30ae3494138674239c8f638556d7f5105e
|
4
|
+
data.tar.gz: 23592e61f76acb0be2a4c60d953a829e8f1d46534633513ac44797a5d3386d2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c8db1d976343d569aa1fec1cee02de2c72947e0937fcd9917f8f6e078204efd373d244954e9e03b8dc7fd20066dcf7533bf4b52c706dba0f5640b60ae0465f0
|
7
|
+
data.tar.gz: 38422c543b159cc7243ac4dcc9139ed95b2583cab7942c60e4c557b89d6a4ffaa8fb1df94286fa3fbe4b918f5c116364d5fbe5c2afa611ba9d46d7ac56c18e61
|
@@ -28,14 +28,14 @@ module GraphQL
|
|
28
28
|
end
|
29
29
|
when nil
|
30
30
|
subject.logger.warn <<~GRAPHQL
|
31
|
-
GraphQL-Ruby's complexity cost system is getting some "breaking fixes" in a future version. See the migration notes at https://graphql-ruby.org/api-
|
31
|
+
GraphQL-Ruby's complexity cost system is getting some "breaking fixes" in a future version. See the migration notes at https://graphql-ruby.org/api-doc/#{GraphQL::VERSION}/GraphQL/Schema.html#complexity_cost_calculation_mode_for-class_method
|
32
32
|
|
33
33
|
To opt into the future behavior, configure your schema (#{subject.schema.name ? subject.schema.name : subject.schema.ancestors}) with:
|
34
34
|
|
35
35
|
complexity_cost_calculation_mode(:future) # or `:legacy`, `:compare`
|
36
36
|
|
37
37
|
GRAPHQL
|
38
|
-
max_possible_complexity
|
38
|
+
max_possible_complexity(mode: :legacy)
|
39
39
|
else
|
40
40
|
raise ArgumentError, "Expected `:future`, `:legacy`, `:compare`, or `nil` from `#{query.schema}.complexity_cost_calculation_mode_for` but got: #{query.schema.complexity_cost_calculation_mode.inspect}"
|
41
41
|
end
|
@@ -31,7 +31,12 @@ module GraphQL
|
|
31
31
|
def fetch(records)
|
32
32
|
record_classes = Set.new.compare_by_identity
|
33
33
|
associated_classes = Set.new.compare_by_identity
|
34
|
+
scoped_fetch = !@scope.nil?
|
34
35
|
records.each do |record|
|
36
|
+
if scoped_fetch
|
37
|
+
assoc = record.association(@association)
|
38
|
+
assoc.reset
|
39
|
+
end
|
35
40
|
if record_classes.add?(record.class)
|
36
41
|
reflection = record.class.reflect_on_association(@association)
|
37
42
|
if !reflection.polymorphic? && reflection.klass
|
@@ -48,9 +53,16 @@ module GraphQL
|
|
48
53
|
|
49
54
|
::ActiveRecord::Associations::Preloader.new(records: records, associations: @association, available_records: available_records, scope: @scope).call
|
50
55
|
|
51
|
-
loaded_associated_records = records.map { |r|
|
56
|
+
loaded_associated_records = records.map { |r|
|
57
|
+
assoc = r.association(@association)
|
58
|
+
lar = assoc.target
|
59
|
+
if scoped_fetch
|
60
|
+
assoc.reset
|
61
|
+
end
|
62
|
+
lar
|
63
|
+
}
|
52
64
|
|
53
|
-
if
|
65
|
+
if !scoped_fetch
|
54
66
|
# Don't cache records loaded via scope because they might have reduced `SELECT`s
|
55
67
|
# Could check .select_values here?
|
56
68
|
records_by_model = {}
|
@@ -9,8 +9,11 @@ module GraphQL
|
|
9
9
|
class NullDataloader < Dataloader
|
10
10
|
# These are all no-ops because code was
|
11
11
|
# executed synchronously.
|
12
|
+
|
13
|
+
def initialize(*); end
|
12
14
|
def run; end
|
13
15
|
def run_isolated; yield; end
|
16
|
+
def clear_cache; end
|
14
17
|
def yield(_source)
|
15
18
|
raise GraphQL::Error, "GraphQL::Dataloader is not running -- add `use GraphQL::Dataloader` to your schema to use Dataloader sources."
|
16
19
|
end
|
@@ -19,6 +22,10 @@ module GraphQL
|
|
19
22
|
yield
|
20
23
|
nil
|
21
24
|
end
|
25
|
+
|
26
|
+
def with(*)
|
27
|
+
raise GraphQL::Error, "GraphQL::Dataloader is not running -- add `use GraphQL::Dataloader` to your schema to use Dataloader sources."
|
28
|
+
end
|
22
29
|
end
|
23
30
|
end
|
24
31
|
end
|
@@ -23,9 +23,9 @@ module GraphQL
|
|
23
23
|
if smallest_depth
|
24
24
|
lazies = lazies_at_depth.delete(smallest_depth)
|
25
25
|
if !lazies.empty?
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
lazies.each do |l|
|
27
|
+
dataloader.append_job { l.value }
|
28
|
+
end
|
29
29
|
# Run lazies _and_ dataloader, see if more are enqueued
|
30
30
|
dataloader.run
|
31
31
|
resolve_each_depth(lazies_at_depth, dataloader)
|
@@ -21,15 +21,25 @@ module GraphQL
|
|
21
21
|
@graphql_metadata = nil
|
22
22
|
@graphql_selections = selections
|
23
23
|
@graphql_is_eager = is_eager
|
24
|
+
@base_path = nil
|
24
25
|
end
|
25
26
|
|
27
|
+
# TODO test full path in Partial
|
28
|
+
attr_writer :base_path
|
29
|
+
|
26
30
|
def path
|
27
31
|
@path ||= build_path([])
|
28
32
|
end
|
29
33
|
|
30
34
|
def build_path(path_array)
|
31
35
|
graphql_result_name && path_array.unshift(graphql_result_name)
|
32
|
-
|
36
|
+
if @graphql_parent
|
37
|
+
@graphql_parent.build_path(path_array)
|
38
|
+
elsif @base_path
|
39
|
+
@base_path + path_array
|
40
|
+
else
|
41
|
+
path_array
|
42
|
+
end
|
33
43
|
end
|
34
44
|
|
35
45
|
attr_accessor :graphql_dead
|
@@ -57,53 +57,142 @@ module GraphQL
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def final_result
|
60
|
-
@response
|
60
|
+
@response.respond_to?(:graphql_result_data) ? @response.graphql_result_data : @response
|
61
61
|
end
|
62
62
|
|
63
63
|
def inspect
|
64
64
|
"#<#{self.class.name} response=#{@response.inspect}>"
|
65
65
|
end
|
66
66
|
|
67
|
-
# This _begins_ the execution. Some deferred work
|
68
|
-
# might be stored up in lazies.
|
69
67
|
# @return [void]
|
70
68
|
def run_eager
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
69
|
+
root_type = query.root_type
|
70
|
+
case query
|
71
|
+
when GraphQL::Query
|
72
|
+
ast_node = query.selected_operation
|
73
|
+
selections = ast_node.selections
|
74
|
+
object = query.root_value
|
75
|
+
is_eager = ast_node.operation_type == "mutation"
|
76
|
+
base_path = nil
|
77
|
+
when GraphQL::Query::Partial
|
78
|
+
ast_node = query.ast_nodes.first
|
79
|
+
selections = query.ast_nodes.map(&:selections).inject(&:+)
|
80
|
+
object = query.object
|
81
|
+
is_eager = false
|
82
|
+
base_path = query.path
|
84
83
|
else
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
84
|
+
raise ArgumentError, "Unexpected Runnable, can't execute: #{query.class} (#{query.inspect})"
|
85
|
+
end
|
86
|
+
object = schema.sync_lazy(object) # TODO test query partial with lazy root object
|
87
|
+
runtime_state = get_current_runtime_state
|
88
|
+
case root_type.kind.name
|
89
|
+
when "OBJECT"
|
90
|
+
object_proxy = root_type.wrap(object, context)
|
91
|
+
object_proxy = schema.sync_lazy(object_proxy)
|
92
|
+
if object_proxy.nil?
|
93
|
+
@response = nil
|
94
|
+
else
|
95
|
+
@response = GraphQLResultHash.new(nil, root_type, object_proxy, nil, false, selections, is_eager, ast_node, nil, nil)
|
96
|
+
@response.base_path = base_path
|
97
|
+
runtime_state.current_result = @response
|
98
|
+
call_method_on_directives(:resolve, object, ast_node.directives) do
|
99
|
+
each_gathered_selections(@response) do |selections, is_selection_array, ordered_result_keys|
|
100
|
+
@response.ordered_result_keys ||= ordered_result_keys
|
101
|
+
if is_selection_array
|
102
|
+
selection_response = GraphQLResultHash.new(nil, root_type, object_proxy, nil, false, selections, is_eager, ast_node, nil, nil)
|
103
|
+
selection_response.ordered_result_keys = ordered_result_keys
|
104
|
+
final_response = @response
|
105
|
+
else
|
106
|
+
selection_response = @response
|
107
|
+
final_response = nil
|
108
|
+
end
|
96
109
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
110
|
+
@dataloader.append_job {
|
111
|
+
evaluate_selections(
|
112
|
+
selections,
|
113
|
+
selection_response,
|
114
|
+
final_response,
|
115
|
+
nil,
|
116
|
+
)
|
117
|
+
}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
when "LIST"
|
122
|
+
inner_type = root_type.unwrap
|
123
|
+
case inner_type.kind.name
|
124
|
+
when "SCALAR", "ENUM"
|
125
|
+
result_name = ast_node.alias || ast_node.name
|
126
|
+
field_defn = query.field_definition
|
127
|
+
owner_type = field_defn.owner
|
128
|
+
selection_result = GraphQLResultHash.new(nil, owner_type, nil, nil, false, EmptyObjects::EMPTY_ARRAY, false, ast_node, nil, nil)
|
129
|
+
selection_result.base_path = base_path
|
130
|
+
selection_result.ordered_result_keys = [result_name]
|
131
|
+
runtime_state = get_current_runtime_state
|
132
|
+
runtime_state.current_result = selection_result
|
133
|
+
runtime_state.current_result_name = result_name
|
134
|
+
continue_value = continue_value(object, field_defn, false, ast_node, result_name, selection_result)
|
135
|
+
if HALT != continue_value
|
136
|
+
continue_field(continue_value, owner_type, field_defn, root_type, ast_node, nil, false, nil, nil, result_name, selection_result, false, runtime_state) # rubocop:disable Metrics/ParameterLists
|
137
|
+
end
|
138
|
+
@response = selection_result[result_name]
|
139
|
+
else
|
140
|
+
@response = GraphQLResultArray.new(nil, root_type, nil, nil, false, selections, false, ast_node, nil, nil)
|
141
|
+
@response.base_path = base_path
|
142
|
+
idx = nil
|
143
|
+
object.each do |inner_value|
|
144
|
+
idx ||= 0
|
145
|
+
this_idx = idx
|
146
|
+
idx += 1
|
147
|
+
@dataloader.append_job do
|
148
|
+
runtime_state.current_result_name = this_idx
|
149
|
+
runtime_state.current_result = @response
|
150
|
+
continue_field(
|
151
|
+
inner_value, root_type, nil, inner_type, nil, @response.graphql_selections, false, object_proxy,
|
152
|
+
nil, this_idx, @response, false, runtime_state
|
103
153
|
)
|
104
|
-
|
154
|
+
end
|
105
155
|
end
|
106
156
|
end
|
157
|
+
when "SCALAR", "ENUM"
|
158
|
+
result_name = ast_node.alias || ast_node.name
|
159
|
+
field_defn = query.field_definition
|
160
|
+
owner_type = field_defn.owner
|
161
|
+
selection_result = GraphQLResultHash.new(nil, owner_type, nil, nil, false, EmptyObjects::EMPTY_ARRAY, false, ast_node, nil, nil)
|
162
|
+
selection_result.ordered_result_keys = [result_name]
|
163
|
+
selection_result.base_path = base_path
|
164
|
+
runtime_state = get_current_runtime_state
|
165
|
+
runtime_state.current_result = selection_result
|
166
|
+
runtime_state.current_result_name = result_name
|
167
|
+
continue_value = continue_value(object, field_defn, false, ast_node, result_name, selection_result)
|
168
|
+
if HALT != continue_value
|
169
|
+
continue_field(continue_value, owner_type, field_defn, query.root_type, ast_node, nil, false, nil, nil, result_name, selection_result, false, runtime_state) # rubocop:disable Metrics/ParameterLists
|
170
|
+
end
|
171
|
+
@response = selection_result[result_name]
|
172
|
+
when "UNION", "INTERFACE"
|
173
|
+
resolved_type, _resolved_obj = resolve_type(root_type, object)
|
174
|
+
resolved_type = schema.sync_lazy(resolved_type)
|
175
|
+
object_proxy = resolved_type.wrap(object, context)
|
176
|
+
object_proxy = schema.sync_lazy(object_proxy)
|
177
|
+
@response = GraphQLResultHash.new(nil, resolved_type, object_proxy, nil, false, selections, false, query.ast_nodes.first, nil, nil)
|
178
|
+
@response.base_path = base_path
|
179
|
+
each_gathered_selections(@response) do |selections, is_selection_array, ordered_result_keys|
|
180
|
+
@response.ordered_result_keys ||= ordered_result_keys
|
181
|
+
if is_selection_array == true
|
182
|
+
raise "This isn't supported yet"
|
183
|
+
end
|
184
|
+
|
185
|
+
@dataloader.append_job {
|
186
|
+
evaluate_selections(
|
187
|
+
selections,
|
188
|
+
@response,
|
189
|
+
nil,
|
190
|
+
runtime_state,
|
191
|
+
)
|
192
|
+
}
|
193
|
+
end
|
194
|
+
else
|
195
|
+
raise "Invariant: unsupported type kind for partial execution: #{root_type.kind.inspect} (#{root_type})"
|
107
196
|
end
|
108
197
|
nil
|
109
198
|
end
|
@@ -209,8 +298,6 @@ module GraphQL
|
|
209
298
|
end
|
210
299
|
|
211
300
|
call_method_on_directives(:resolve, selections_result.graphql_application_value, directives) do
|
212
|
-
finished_jobs = 0
|
213
|
-
enqueued_jobs = gathered_selections.size
|
214
301
|
gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
|
215
302
|
# Field resolution may pause the fiber,
|
216
303
|
# so it wouldn't get to the `Resolve` call that happens below.
|
@@ -221,12 +308,6 @@ module GraphQL
|
|
221
308
|
evaluate_selection(
|
222
309
|
result_name, field_ast_nodes_or_ast_node, selections_result
|
223
310
|
)
|
224
|
-
finished_jobs += 1
|
225
|
-
if finished_jobs == enqueued_jobs
|
226
|
-
if target_result
|
227
|
-
selections_result.merge_into(target_result)
|
228
|
-
end
|
229
|
-
end
|
230
311
|
@dataloader.clear_cache
|
231
312
|
}
|
232
313
|
else
|
@@ -234,15 +315,12 @@ module GraphQL
|
|
234
315
|
evaluate_selection(
|
235
316
|
result_name, field_ast_nodes_or_ast_node, selections_result
|
236
317
|
)
|
237
|
-
finished_jobs += 1
|
238
|
-
if finished_jobs == enqueued_jobs
|
239
|
-
if target_result
|
240
|
-
selections_result.merge_into(target_result)
|
241
|
-
end
|
242
|
-
end
|
243
318
|
}
|
244
319
|
end
|
245
320
|
end
|
321
|
+
if target_result
|
322
|
+
selections_result.merge_into(target_result)
|
323
|
+
end
|
246
324
|
selections_result
|
247
325
|
end
|
248
326
|
end
|
@@ -468,7 +546,7 @@ module GraphQL
|
|
468
546
|
path
|
469
547
|
end
|
470
548
|
|
471
|
-
HALT = Object.new
|
549
|
+
HALT = Object.new.freeze
|
472
550
|
def continue_value(value, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
|
473
551
|
case value
|
474
552
|
when nil
|
@@ -586,6 +664,8 @@ module GraphQL
|
|
586
664
|
when "SCALAR", "ENUM"
|
587
665
|
r = begin
|
588
666
|
current_type.coerce_result(value, context)
|
667
|
+
rescue GraphQL::ExecutionError => ex_err
|
668
|
+
return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
|
589
669
|
rescue StandardError => err
|
590
670
|
query.handle_or_reraise(err)
|
591
671
|
end
|
@@ -737,6 +817,13 @@ module GraphQL
|
|
737
817
|
else
|
738
818
|
dir_defn = @schema_directives.fetch(dir_node.name)
|
739
819
|
raw_dir_args = arguments(nil, dir_defn, dir_node)
|
820
|
+
if !raw_dir_args.is_a?(GraphQL::ExecutionError)
|
821
|
+
begin
|
822
|
+
dir_defn.validate!(raw_dir_args, context)
|
823
|
+
rescue GraphQL::ExecutionError => err
|
824
|
+
raw_dir_args = err
|
825
|
+
end
|
826
|
+
end
|
740
827
|
dir_args = continue_value(
|
741
828
|
raw_dir_args, # value
|
742
829
|
nil, # field
|
@@ -26,7 +26,7 @@ module GraphQL
|
|
26
26
|
query = case opts
|
27
27
|
when Hash
|
28
28
|
schema.query_class.new(schema, nil, **opts)
|
29
|
-
when GraphQL::Query
|
29
|
+
when GraphQL::Query, GraphQL::Query::Partial
|
30
30
|
opts
|
31
31
|
else
|
32
32
|
raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})"
|
@@ -20,6 +20,11 @@ module GraphQL
|
|
20
20
|
@finished
|
21
21
|
end
|
22
22
|
|
23
|
+
def freeze
|
24
|
+
@scanner = nil
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
23
28
|
attr_reader :pos, :tokens_count
|
24
29
|
|
25
30
|
def advance
|
@@ -242,7 +247,7 @@ module GraphQL
|
|
242
247
|
:SCALAR,
|
243
248
|
nil,
|
244
249
|
:FRAGMENT
|
245
|
-
]
|
250
|
+
].freeze
|
246
251
|
|
247
252
|
# This produces a unique integer for bytes 2 and 3 of each keyword string
|
248
253
|
# See https://tenderlovemaking.com/2023/09/02/fast-tokenizers-with-stringscanner.html
|
@@ -271,7 +276,8 @@ module GraphQL
|
|
271
276
|
PUNCTUATION_NAME_FOR_BYTE = Punctuation.constants.each_with_object([]) { |name, arr|
|
272
277
|
punct = Punctuation.const_get(name)
|
273
278
|
arr[punct.ord] = name
|
274
|
-
}
|
279
|
+
}.freeze
|
280
|
+
|
275
281
|
|
276
282
|
QUOTE = '"'
|
277
283
|
UNICODE_DIGIT = /[0-9A-Za-z]/
|
@@ -321,6 +327,7 @@ module GraphQL
|
|
321
327
|
punct = Punctuation.const_get(punct_name)
|
322
328
|
FIRST_BYTES[punct.ord] = ByteFor::PUNCTUATION
|
323
329
|
end
|
330
|
+
FIRST_BYTES.freeze
|
324
331
|
|
325
332
|
|
326
333
|
# Replace any escaped unicode or whitespace with the _actual_ characters
|
@@ -83,7 +83,11 @@ module GraphQL
|
|
83
83
|
|
84
84
|
def to_query_string(printer: GraphQL::Language::Printer.new)
|
85
85
|
if printer.is_a?(GraphQL::Language::Printer)
|
86
|
-
|
86
|
+
if frozen?
|
87
|
+
@query_string || printer.print(self)
|
88
|
+
else
|
89
|
+
@query_string ||= printer.print(self)
|
90
|
+
end
|
87
91
|
else
|
88
92
|
printer.print(self)
|
89
93
|
end
|
@@ -84,7 +84,7 @@ module GraphQL
|
|
84
84
|
|
85
85
|
attr_writer :types
|
86
86
|
|
87
|
-
RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path])
|
87
|
+
RUNTIME_METADATA_KEYS = Set.new([:current_object, :current_arguments, :current_field, :current_path]).freeze
|
88
88
|
# @!method []=(key, value)
|
89
89
|
# Reassign `key` to the hash passed to {Schema#execute} as `context:`
|
90
90
|
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module GraphQL
|
3
|
+
class Query
|
4
|
+
# This class is _like_ a {GraphQL::Query}, except it can run on an arbitrary path within a query string.
|
5
|
+
#
|
6
|
+
# It depends on a "parent" {Query}.
|
7
|
+
#
|
8
|
+
# During execution, it calls query-related tracing hooks but passes itself as `query:`.
|
9
|
+
#
|
10
|
+
# The {Partial} will use your {Schema.resolve_type} hook to find the right GraphQL type to use for
|
11
|
+
# `object` in some cases.
|
12
|
+
#
|
13
|
+
# @see Query#run_partials Run via {Query#run_partials}
|
14
|
+
class Partial
|
15
|
+
include Query::Runnable
|
16
|
+
|
17
|
+
# @param path [Array<String, Integer>] A path in `query.query_string` to start executing from
|
18
|
+
# @param object [Object] A starting object for execution
|
19
|
+
# @param query [GraphQL::Query] A full query instance that this partial is based on. Caches are shared.
|
20
|
+
# @param context [Hash] Extra context values to merge into `query.context`, if provided
|
21
|
+
# @param fragment_node [GraphQL::Language::Nodes::InlineFragment, GraphQL::Language::Nodes::FragmentDefinition]
|
22
|
+
def initialize(path: nil, object:, query:, context: nil, fragment_node: nil, type: nil)
|
23
|
+
@path = path
|
24
|
+
@object = object
|
25
|
+
@query = query
|
26
|
+
@schema = query.schema
|
27
|
+
context_vals = @query.context.to_h
|
28
|
+
if context
|
29
|
+
context_vals = context_vals.merge(context)
|
30
|
+
end
|
31
|
+
@context = GraphQL::Query::Context.new(query: self, schema: @query.schema, values: context_vals)
|
32
|
+
@multiplex = nil
|
33
|
+
@result_values = nil
|
34
|
+
@result = nil
|
35
|
+
|
36
|
+
if fragment_node
|
37
|
+
@ast_nodes = [fragment_node]
|
38
|
+
@root_type = type || raise(ArgumentError, "Pass `type:` when using `node:`")
|
39
|
+
# This is only used when `@leaf`
|
40
|
+
@field_definition = nil
|
41
|
+
elsif path.nil?
|
42
|
+
raise ArgumentError, "`path:` is required if `node:` is not given; add `path:`"
|
43
|
+
else
|
44
|
+
set_type_info_from_path
|
45
|
+
end
|
46
|
+
|
47
|
+
@leaf = @root_type.unwrap.kind.leaf?
|
48
|
+
end
|
49
|
+
|
50
|
+
def leaf?
|
51
|
+
@leaf
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_reader :context, :query, :ast_nodes, :root_type, :object, :field_definition, :path, :schema
|
55
|
+
|
56
|
+
attr_accessor :multiplex, :result_values
|
57
|
+
|
58
|
+
class Result < GraphQL::Query::Result
|
59
|
+
def path
|
60
|
+
@query.path
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [GraphQL::Query::Partial]
|
64
|
+
def partial
|
65
|
+
@query
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def result
|
70
|
+
@result ||= Result.new(query: self, values: result_values)
|
71
|
+
end
|
72
|
+
|
73
|
+
def current_trace
|
74
|
+
@query.current_trace
|
75
|
+
end
|
76
|
+
|
77
|
+
def types
|
78
|
+
@query.types
|
79
|
+
end
|
80
|
+
|
81
|
+
def resolve_type(...)
|
82
|
+
@query.resolve_type(...)
|
83
|
+
end
|
84
|
+
|
85
|
+
def variables
|
86
|
+
@query.variables
|
87
|
+
end
|
88
|
+
|
89
|
+
def fragments
|
90
|
+
@query.fragments
|
91
|
+
end
|
92
|
+
|
93
|
+
def valid?
|
94
|
+
@query.valid?
|
95
|
+
end
|
96
|
+
|
97
|
+
def analyzers
|
98
|
+
EmptyObjects::EMPTY_ARRAY
|
99
|
+
end
|
100
|
+
|
101
|
+
def analysis_errors=(_ignored)
|
102
|
+
# pass
|
103
|
+
end
|
104
|
+
|
105
|
+
def subscription?
|
106
|
+
@query.subscription?
|
107
|
+
end
|
108
|
+
|
109
|
+
def selected_operation
|
110
|
+
ast_nodes.first
|
111
|
+
end
|
112
|
+
|
113
|
+
def static_errors
|
114
|
+
@query.static_errors
|
115
|
+
end
|
116
|
+
|
117
|
+
def selected_operation_name
|
118
|
+
@query.selected_operation_name
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def set_type_info_from_path
|
124
|
+
selections = [@query.selected_operation]
|
125
|
+
type = @query.root_type
|
126
|
+
parent_type = nil
|
127
|
+
field_defn = nil
|
128
|
+
|
129
|
+
@path.each do |name_in_doc|
|
130
|
+
if name_in_doc.is_a?(Integer)
|
131
|
+
if type.list?
|
132
|
+
type = type.unwrap
|
133
|
+
next
|
134
|
+
else
|
135
|
+
raise ArgumentError, "Received path with index `#{name_in_doc}`, but type wasn't a list. Type: #{type.to_type_signature}, path: #{@path}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
next_selections = []
|
140
|
+
selections.each do |selection|
|
141
|
+
selections_to_check = []
|
142
|
+
selections_to_check.concat(selection.selections)
|
143
|
+
while (sel = selections_to_check.shift)
|
144
|
+
case sel
|
145
|
+
when GraphQL::Language::Nodes::InlineFragment
|
146
|
+
selections_to_check.concat(sel.selections)
|
147
|
+
when GraphQL::Language::Nodes::FragmentSpread
|
148
|
+
fragment = @query.fragments[sel.name]
|
149
|
+
selections_to_check.concat(fragment.selections)
|
150
|
+
when GraphQL::Language::Nodes::Field
|
151
|
+
if sel.alias == name_in_doc || sel.name == name_in_doc
|
152
|
+
next_selections << sel
|
153
|
+
end
|
154
|
+
else
|
155
|
+
raise "Unexpected selection in partial path: #{sel.class}, #{sel.inspect}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
if next_selections.empty?
|
161
|
+
raise ArgumentError, "Path `#{@path.inspect}` is not present in this query. `#{name_in_doc.inspect}` was not found. Try a different path or rewrite the query to include it."
|
162
|
+
end
|
163
|
+
field_name = next_selections.first.name
|
164
|
+
field_defn = @schema.get_field(type, field_name, @query.context) || raise("Invariant: no field called #{field_name} on #{type.graphql_name}")
|
165
|
+
parent_type = type
|
166
|
+
type = field_defn.type
|
167
|
+
if type.non_null?
|
168
|
+
type = type.of_type
|
169
|
+
end
|
170
|
+
selections = next_selections
|
171
|
+
end
|
172
|
+
|
173
|
+
@ast_nodes = selections
|
174
|
+
@root_type = type
|
175
|
+
@field_definition = field_defn
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|