graphql 2.3.5 → 2.3.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,282 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Analysis
4
+ # Depth first traversal through a query AST, calling AST analyzers
5
+ # along the way.
6
+ #
7
+ # The visitor is a special case of GraphQL::Language::StaticVisitor, visiting
8
+ # only the selected operation, providing helpers for common use cases such
9
+ # as skipped fields and visiting fragment spreads.
10
+ #
11
+ # @see {GraphQL::Analysis::Analyzer} AST Analyzers for queries
12
+ class Visitor < GraphQL::Language::StaticVisitor
13
+ def initialize(query:, analyzers:)
14
+ @analyzers = analyzers
15
+ @path = []
16
+ @object_types = []
17
+ @directives = []
18
+ @field_definitions = []
19
+ @argument_definitions = []
20
+ @directive_definitions = []
21
+ @rescued_errors = []
22
+ @query = query
23
+ @schema = query.schema
24
+ @response_path = []
25
+ @skip_stack = [false]
26
+ super(query.selected_operation)
27
+ end
28
+
29
+ # @return [GraphQL::Query] the query being visited
30
+ attr_reader :query
31
+
32
+ # @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
33
+ attr_reader :object_types
34
+
35
+ # @return [Array<GraphQL::AnalysisError]
36
+ attr_reader :rescued_errors
37
+
38
+ def visit
39
+ return unless @document
40
+ super
41
+ end
42
+
43
+ # Visit Helpers
44
+
45
+ # @return [GraphQL::Execution::Interpreter::Arguments] Arguments for this node, merging default values, literal values and query variables
46
+ # @see {GraphQL::Query#arguments_for}
47
+ def arguments_for(ast_node, field_definition)
48
+ @query.arguments_for(ast_node, field_definition)
49
+ end
50
+
51
+ # @return [Boolean] If the visitor is currently inside a fragment definition
52
+ def visiting_fragment_definition?
53
+ @in_fragment_def
54
+ end
55
+
56
+ # @return [Boolean] If the current node should be skipped because of a skip or include directive
57
+ def skipping?
58
+ @skipping
59
+ end
60
+
61
+ # @return [Array<String>] The path to the response key for the current field
62
+ def response_path
63
+ @response_path.dup
64
+ end
65
+
66
+ # Visitor Hooks
67
+ [
68
+ :operation_definition, :fragment_definition,
69
+ :inline_fragment, :field, :directive, :argument, :fragment_spread
70
+ ].each do |node_type|
71
+ module_eval <<-RUBY, __FILE__, __LINE__
72
+ def call_on_enter_#{node_type}(node, parent)
73
+ @analyzers.each do |a|
74
+ begin
75
+ a.on_enter_#{node_type}(node, parent, self)
76
+ rescue AnalysisError => err
77
+ @rescued_errors << err
78
+ end
79
+ end
80
+ end
81
+
82
+ def call_on_leave_#{node_type}(node, parent)
83
+ @analyzers.each do |a|
84
+ begin
85
+ a.on_leave_#{node_type}(node, parent, self)
86
+ rescue AnalysisError => err
87
+ @rescued_errors << err
88
+ end
89
+ end
90
+ end
91
+
92
+ RUBY
93
+ end
94
+
95
+ def on_operation_definition(node, parent)
96
+ object_type = @schema.root_type_for_operation(node.operation_type)
97
+ @object_types.push(object_type)
98
+ @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
99
+ call_on_enter_operation_definition(node, parent)
100
+ super
101
+ call_on_leave_operation_definition(node, parent)
102
+ @object_types.pop
103
+ @path.pop
104
+ end
105
+
106
+ def on_fragment_definition(node, parent)
107
+ on_fragment_with_type(node) do
108
+ @path.push("fragment #{node.name}")
109
+ @in_fragment_def = false
110
+ call_on_enter_fragment_definition(node, parent)
111
+ super
112
+ @in_fragment_def = false
113
+ call_on_leave_fragment_definition(node, parent)
114
+ end
115
+ end
116
+
117
+ def on_inline_fragment(node, parent)
118
+ on_fragment_with_type(node) do
119
+ @path.push("...#{node.type ? " on #{node.type.name}" : ""}")
120
+ @skipping = @skip_stack.last || skip?(node)
121
+ @skip_stack << @skipping
122
+
123
+ call_on_enter_inline_fragment(node, parent)
124
+ super
125
+ @skipping = @skip_stack.pop
126
+ call_on_leave_inline_fragment(node, parent)
127
+ end
128
+ end
129
+
130
+ def on_field(node, parent)
131
+ @response_path.push(node.alias || node.name)
132
+ parent_type = @object_types.last
133
+ # This could be nil if the previous field wasn't found:
134
+ field_definition = parent_type && @schema.get_field(parent_type, node.name, @query.context)
135
+ @field_definitions.push(field_definition)
136
+ if !field_definition.nil?
137
+ next_object_type = field_definition.type.unwrap
138
+ @object_types.push(next_object_type)
139
+ else
140
+ @object_types.push(nil)
141
+ end
142
+ @path.push(node.alias || node.name)
143
+
144
+ @skipping = @skip_stack.last || skip?(node)
145
+ @skip_stack << @skipping
146
+
147
+ call_on_enter_field(node, parent)
148
+ super
149
+ @skipping = @skip_stack.pop
150
+ call_on_leave_field(node, parent)
151
+ @response_path.pop
152
+ @field_definitions.pop
153
+ @object_types.pop
154
+ @path.pop
155
+ end
156
+
157
+ def on_directive(node, parent)
158
+ directive_defn = @schema.directives[node.name]
159
+ @directive_definitions.push(directive_defn)
160
+ call_on_enter_directive(node, parent)
161
+ super
162
+ call_on_leave_directive(node, parent)
163
+ @directive_definitions.pop
164
+ end
165
+
166
+ def on_argument(node, parent)
167
+ argument_defn = if (arg = @argument_definitions.last)
168
+ arg_type = arg.type.unwrap
169
+ if arg_type.kind.input_object?
170
+ arg_type.get_argument(node.name, @query.context)
171
+ else
172
+ nil
173
+ end
174
+ elsif (directive_defn = @directive_definitions.last)
175
+ directive_defn.get_argument(node.name, @query.context)
176
+ elsif (field_defn = @field_definitions.last)
177
+ field_defn.get_argument(node.name, @query.context)
178
+ else
179
+ nil
180
+ end
181
+
182
+ @argument_definitions.push(argument_defn)
183
+ @path.push(node.name)
184
+ call_on_enter_argument(node, parent)
185
+ super
186
+ call_on_leave_argument(node, parent)
187
+ @argument_definitions.pop
188
+ @path.pop
189
+ end
190
+
191
+ def on_fragment_spread(node, parent)
192
+ @path.push("... #{node.name}")
193
+ @skipping = @skip_stack.last || skip?(node)
194
+ @skip_stack << @skipping
195
+
196
+ call_on_enter_fragment_spread(node, parent)
197
+ enter_fragment_spread_inline(node)
198
+ super
199
+ @skipping = @skip_stack.pop
200
+ leave_fragment_spread_inline(node)
201
+ call_on_leave_fragment_spread(node, parent)
202
+ @path.pop
203
+ end
204
+
205
+ # @return [GraphQL::BaseType] The current object type
206
+ def type_definition
207
+ @object_types.last
208
+ end
209
+
210
+ # @return [GraphQL::BaseType] The type which the current type came from
211
+ def parent_type_definition
212
+ @object_types[-2]
213
+ end
214
+
215
+ # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
216
+ def field_definition
217
+ @field_definitions.last
218
+ end
219
+
220
+ # @return [GraphQL::Field, nil] The GraphQL field which returned the object that the current field belongs to
221
+ def previous_field_definition
222
+ @field_definitions[-2]
223
+ end
224
+
225
+ # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
226
+ def directive_definition
227
+ @directive_definitions.last
228
+ end
229
+
230
+ # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
231
+ def argument_definition
232
+ @argument_definitions.last
233
+ end
234
+
235
+ # @return [GraphQL::Argument, nil] The previous GraphQL argument
236
+ def previous_argument_definition
237
+ @argument_definitions[-2]
238
+ end
239
+
240
+ private
241
+
242
+ # Visit a fragment spread inline instead of visiting the definition
243
+ # by itself.
244
+ def enter_fragment_spread_inline(fragment_spread)
245
+ fragment_def = query.fragments[fragment_spread.name]
246
+
247
+ object_type = if fragment_def.type
248
+ @query.warden.get_type(fragment_def.type.name)
249
+ else
250
+ object_types.last
251
+ end
252
+
253
+ object_types << object_type
254
+
255
+ on_fragment_definition_children(fragment_def)
256
+ end
257
+
258
+ # Visit a fragment spread inline instead of visiting the definition
259
+ # by itself.
260
+ def leave_fragment_spread_inline(_fragment_spread)
261
+ object_types.pop
262
+ end
263
+
264
+ def skip?(ast_node)
265
+ dir = ast_node.directives
266
+ dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
267
+ end
268
+
269
+ def on_fragment_with_type(node)
270
+ object_type = if node.type
271
+ @query.warden.get_type(node.type.name)
272
+ else
273
+ @object_types.last
274
+ end
275
+ @object_types.push(object_type)
276
+ yield(node)
277
+ @object_types.pop
278
+ @path.pop
279
+ end
280
+ end
281
+ end
282
+ end
@@ -1,2 +1,93 @@
1
1
  # frozen_string_literal: true
2
- require "graphql/analysis/ast"
2
+ require "graphql/analysis/visitor"
3
+ require "graphql/analysis/analyzer"
4
+ require "graphql/analysis/field_usage"
5
+ require "graphql/analysis/query_complexity"
6
+ require "graphql/analysis/max_query_complexity"
7
+ require "graphql/analysis/query_depth"
8
+ require "graphql/analysis/max_query_depth"
9
+ require "timeout"
10
+
11
+ module GraphQL
12
+ module Analysis
13
+ AST = self
14
+ module_function
15
+ # Analyze a multiplex, and all queries within.
16
+ # Multiplex analyzers are ran for all queries, keeping state.
17
+ # Query analyzers are ran per query, without carrying state between queries.
18
+ #
19
+ # @param multiplex [GraphQL::Execution::Multiplex]
20
+ # @param analyzers [Array<GraphQL::Analysis::Analyzer>]
21
+ # @return [Array<Any>] Results from multiplex analyzers
22
+ def analyze_multiplex(multiplex, analyzers)
23
+ multiplex_analyzers = analyzers.map { |analyzer| analyzer.new(multiplex) }
24
+
25
+ multiplex.current_trace.analyze_multiplex(multiplex: multiplex) do
26
+ query_results = multiplex.queries.map do |query|
27
+ if query.valid?
28
+ analyze_query(
29
+ query,
30
+ query.analyzers,
31
+ multiplex_analyzers: multiplex_analyzers
32
+ )
33
+ else
34
+ []
35
+ end
36
+ end
37
+
38
+ multiplex_results = multiplex_analyzers.map(&:result)
39
+ multiplex_errors = analysis_errors(multiplex_results)
40
+
41
+ multiplex.queries.each_with_index do |query, idx|
42
+ query.analysis_errors = multiplex_errors + analysis_errors(query_results[idx])
43
+ end
44
+ multiplex_results
45
+ end
46
+ end
47
+
48
+ # @param query [GraphQL::Query]
49
+ # @param analyzers [Array<GraphQL::Analysis::Analyzer>]
50
+ # @return [Array<Any>] Results from those analyzers
51
+ def analyze_query(query, analyzers, multiplex_analyzers: [])
52
+ query.current_trace.analyze_query(query: query) do
53
+ query_analyzers = analyzers
54
+ .map { |analyzer| analyzer.new(query) }
55
+ .tap { _1.select!(&:analyze?) }
56
+
57
+ analyzers_to_run = query_analyzers + multiplex_analyzers
58
+ if analyzers_to_run.any?
59
+
60
+ analyzers_to_run.select!(&:visit?)
61
+ if analyzers_to_run.any?
62
+ visitor = GraphQL::Analysis::Visitor.new(
63
+ query: query,
64
+ analyzers: analyzers_to_run
65
+ )
66
+
67
+ # `nil` or `0` causes no timeout
68
+ Timeout::timeout(query.validate_timeout_remaining) do
69
+ visitor.visit
70
+ end
71
+
72
+ if visitor.rescued_errors.any?
73
+ return visitor.rescued_errors
74
+ end
75
+ end
76
+
77
+ query_analyzers.map(&:result)
78
+ else
79
+ []
80
+ end
81
+ end
82
+ rescue Timeout::Error
83
+ [GraphQL::AnalysisError.new("Timeout on validation of query")]
84
+ rescue GraphQL::UnauthorizedError
85
+ # This error was raised during analysis and will be returned the client before execution
86
+ []
87
+ end
88
+
89
+ def analysis_errors(results)
90
+ results.flatten.tap { _1.select! { |r| r.is_a?(GraphQL::AnalysisError) } }
91
+ end
92
+ end
93
+ end
@@ -21,6 +21,7 @@ module GraphQL
21
21
  manager = spawn_fiber do
22
22
  while first_pass || job_fibers.any?
23
23
  first_pass = false
24
+ fiber_vars = get_fiber_variables
24
25
 
25
26
  while (f = (job_fibers.shift || spawn_job_fiber))
26
27
  if f.alive?
@@ -34,6 +35,7 @@ module GraphQL
34
35
  next_job_fibers.clear
35
36
 
36
37
  Sync do |root_task|
38
+ set_fiber_variables(fiber_vars)
37
39
  while source_tasks.any? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) }
38
40
  while (task = source_tasks.shift || spawn_source_task(root_task, sources_condition))
39
41
  if task.alive?
@@ -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::AST::MaxQueryComplexity]
43
+ multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity]
44
44
  end
45
45
 
46
46
  schema.analysis_engine.analyze_multiplex(multiplex, multiplex_analyzers)
@@ -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::AST::MaxQueryDepth
103
+ qa << GraphQL::Analysis::MaxQueryDepth
104
104
  end
105
105
  if max_complexity
106
- qa << GraphQL::Analysis::AST::MaxQueryComplexity
106
+ qa << GraphQL::Analysis::MaxQueryComplexity
107
107
  end
108
108
  qa
109
109
  else
@@ -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 = custom_value.each_with_index.map { |custom_val, idx|
316
- id = coerced_value[idx]
317
- load_method_owner.authorize_application_object(self, id, context, custom_val)
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 = coerced_value.map { |val| load_method_owner.load_and_authorize_application_object(self, val, context) }
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
@@ -41,6 +41,14 @@ module GraphQL
41
41
  end
42
42
  end
43
43
 
44
+ def directives
45
+ if @resolver_class
46
+ @resolver_class.directives
47
+ else
48
+ super
49
+ end
50
+ end
51
+
44
52
  # @return [Class] The thing this field was defined on (type, mutation, resolver)
45
53
  attr_accessor :owner
46
54
 
@@ -25,6 +25,7 @@ module GraphQL
25
25
  extend GraphQL::Schema::Member::HasValidators
26
26
  include Schema::Member::HasPath
27
27
  extend Schema::Member::HasPath
28
+ extend Schema::Member::HasDirectives
28
29
 
29
30
  # @param object [Object] The application object that this field is being resolved on
30
31
  # @param context [GraphQL::Query::Context]
@@ -9,7 +9,7 @@ module GraphQL
9
9
  # Assign the result to `context.namespace(:subscriptions)[:subscription_broadcastable]`
10
10
  # @api private
11
11
  # @see Subscriptions#broadcastable? for a public API
12
- class BroadcastAnalyzer < GraphQL::Analysis::AST::Analyzer
12
+ class BroadcastAnalyzer < GraphQL::Analysis::Analyzer
13
13
  def initialize(subject)
14
14
  super
15
15
  @default_broadcastable = subject.schema.subscriptions.default_broadcastable
@@ -235,7 +235,7 @@ module GraphQL
235
235
  if !query.valid?
236
236
  raise "Invalid query: #{query.validation_errors.map(&:to_h).inspect}"
237
237
  end
238
- GraphQL::Analysis::AST.analyze_query(query, @schema.query_analyzers)
238
+ GraphQL::Analysis.analyze_query(query, @schema.query_analyzers)
239
239
  query.context.namespace(:subscriptions)[:subscription_broadcastable]
240
240
  end
241
241
 
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.3.5"
3
+ VERSION = "2.3.6"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.5
4
+ version: 2.3.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-06-13 00:00:00.000000000 Z
11
+ date: 2024-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -306,14 +306,13 @@ files:
306
306
  - lib/generators/graphql/union_generator.rb
307
307
  - lib/graphql.rb
308
308
  - lib/graphql/analysis.rb
309
- - lib/graphql/analysis/ast.rb
310
- - lib/graphql/analysis/ast/analyzer.rb
311
- - lib/graphql/analysis/ast/field_usage.rb
312
- - lib/graphql/analysis/ast/max_query_complexity.rb
313
- - lib/graphql/analysis/ast/max_query_depth.rb
314
- - lib/graphql/analysis/ast/query_complexity.rb
315
- - lib/graphql/analysis/ast/query_depth.rb
316
- - lib/graphql/analysis/ast/visitor.rb
309
+ - lib/graphql/analysis/analyzer.rb
310
+ - lib/graphql/analysis/field_usage.rb
311
+ - lib/graphql/analysis/max_query_complexity.rb
312
+ - lib/graphql/analysis/max_query_depth.rb
313
+ - lib/graphql/analysis/query_complexity.rb
314
+ - lib/graphql/analysis/query_depth.rb
315
+ - lib/graphql/analysis/visitor.rb
317
316
  - lib/graphql/analysis_error.rb
318
317
  - lib/graphql/backtrace.rb
319
318
  - lib/graphql/backtrace/inspect_result.rb
@@ -645,7 +644,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
645
644
  - !ruby/object:Gem::Version
646
645
  version: '0'
647
646
  requirements: []
648
- rubygems_version: 3.5.3
647
+ rubygems_version: 3.5.12
649
648
  signing_key:
650
649
  specification_version: 4
651
650
  summary: A GraphQL language and runtime for Ruby
@@ -1,91 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- module AST
5
- # Query analyzer for query ASTs. Query analyzers respond to visitor style methods
6
- # but are prefixed by `enter` and `leave`.
7
- #
8
- # When an analyzer is initialized with a Multiplex, you can always get the current query from
9
- # `visitor.query` in the visit methods.
10
- #
11
- # @param [GraphQL::Query, GraphQL::Execution::Multiplex] The query or multiplex to analyze
12
- class Analyzer
13
- def initialize(subject)
14
- @subject = subject
15
-
16
- if subject.is_a?(GraphQL::Query)
17
- @query = subject
18
- @multiplex = nil
19
- else
20
- @multiplex = subject
21
- @query = nil
22
- end
23
- end
24
-
25
- # Analyzer hook to decide at analysis time whether a query should
26
- # be analyzed or not.
27
- # @return [Boolean] If the query should be analyzed or not
28
- def analyze?
29
- true
30
- end
31
-
32
- # Analyzer hook to decide at analysis time whether analysis
33
- # requires a visitor pass; can be disabled for precomputed results.
34
- # @return [Boolean] If analysis requires visitation or not
35
- def visit?
36
- true
37
- end
38
-
39
- # The result for this analyzer. Returning {GraphQL::AnalysisError} results
40
- # in a query error.
41
- # @return [Any] The analyzer result
42
- def result
43
- raise GraphQL::RequiredImplementationMissingError
44
- end
45
-
46
- class << self
47
- private
48
-
49
- def build_visitor_hooks(member_name)
50
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
51
- def on_enter_#{member_name}(node, parent, visitor)
52
- end
53
-
54
- def on_leave_#{member_name}(node, parent, visitor)
55
- end
56
- EOS
57
- end
58
- end
59
-
60
- build_visitor_hooks :argument
61
- build_visitor_hooks :directive
62
- build_visitor_hooks :document
63
- build_visitor_hooks :enum
64
- build_visitor_hooks :field
65
- build_visitor_hooks :fragment_spread
66
- build_visitor_hooks :inline_fragment
67
- build_visitor_hooks :input_object
68
- build_visitor_hooks :list_type
69
- build_visitor_hooks :non_null_type
70
- build_visitor_hooks :null_value
71
- build_visitor_hooks :operation_definition
72
- build_visitor_hooks :type_name
73
- build_visitor_hooks :variable_definition
74
- build_visitor_hooks :variable_identifier
75
- build_visitor_hooks :abstract_node
76
-
77
- protected
78
-
79
- # @return [GraphQL::Query, GraphQL::Execution::Multiplex] Whatever this analyzer is analyzing
80
- attr_reader :subject
81
-
82
- # @return [GraphQL::Query, nil] `nil` if this analyzer is visiting a multiplex
83
- # (When this is `nil`, use `visitor.query` inside visit methods to get the current query)
84
- attr_reader :query
85
-
86
- # @return [GraphQL::Execution::Multiplex, nil] `nil` if this analyzer is visiting a query
87
- attr_reader :multiplex
88
- end
89
- end
90
- end
91
- end