graphql 2.3.5 → 2.3.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- module AST
5
- class FieldUsage < Analyzer
6
- def initialize(query)
7
- super
8
- @used_fields = Set.new
9
- @used_deprecated_fields = Set.new
10
- @used_deprecated_arguments = Set.new
11
- @used_deprecated_enum_values = Set.new
12
- end
13
-
14
- def on_leave_field(node, parent, visitor)
15
- field_defn = visitor.field_definition
16
- field = "#{visitor.parent_type_definition.graphql_name}.#{field_defn.graphql_name}"
17
- @used_fields << field
18
- @used_deprecated_fields << field if field_defn.deprecation_reason
19
- arguments = visitor.query.arguments_for(node, field_defn)
20
- # If there was an error when preparing this argument object,
21
- # then this might be an error or something:
22
- if arguments.respond_to?(:argument_values)
23
- extract_deprecated_arguments(arguments.argument_values)
24
- end
25
- end
26
-
27
- def result
28
- {
29
- used_fields: @used_fields.to_a,
30
- used_deprecated_fields: @used_deprecated_fields.to_a,
31
- used_deprecated_arguments: @used_deprecated_arguments.to_a,
32
- used_deprecated_enum_values: @used_deprecated_enum_values.to_a,
33
- }
34
- end
35
-
36
- private
37
-
38
- def extract_deprecated_arguments(argument_values)
39
- argument_values.each_pair do |_argument_name, argument|
40
- if argument.definition.deprecation_reason
41
- @used_deprecated_arguments << argument.definition.path
42
- end
43
-
44
- arg_val = argument.value
45
-
46
- next if arg_val.nil?
47
-
48
- argument_type = argument.definition.type
49
- if argument_type.non_null?
50
- argument_type = argument_type.of_type
51
- end
52
-
53
- if argument_type.kind.input_object?
54
- extract_deprecated_arguments(argument.original_value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance
55
- elsif argument_type.kind.enum?
56
- extract_deprecated_enum_value(argument_type, arg_val)
57
- elsif argument_type.list?
58
- inner_type = argument_type.unwrap
59
- case inner_type.kind
60
- when TypeKinds::INPUT_OBJECT
61
- argument.original_value.each do |value|
62
- extract_deprecated_arguments(value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance
63
- end
64
- when TypeKinds::ENUM
65
- arg_val.each do |value|
66
- extract_deprecated_enum_value(inner_type, value)
67
- end
68
- else
69
- # Not a kind of input that we track
70
- end
71
- end
72
- end
73
- end
74
-
75
- def extract_deprecated_enum_value(enum_type, value)
76
- enum_value = @query.warden.enum_values(enum_type).find { |ev| ev.value == value }
77
- if enum_value&.deprecation_reason
78
- @used_deprecated_enum_values << enum_value.path
79
- end
80
- end
81
- end
82
- end
83
- end
84
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- module AST
5
- # Used under the hood to implement complexity validation,
6
- # see {Schema#max_complexity} and {Query#max_complexity}
7
- class MaxQueryComplexity < QueryComplexity
8
- def result
9
- return if subject.max_complexity.nil?
10
-
11
- total_complexity = max_possible_complexity
12
-
13
- if total_complexity > subject.max_complexity
14
- GraphQL::AnalysisError.new("Query has complexity of #{total_complexity}, which exceeds max complexity of #{subject.max_complexity}")
15
- else
16
- nil
17
- end
18
- end
19
- end
20
- end
21
- end
22
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- module AST
5
- class MaxQueryDepth < QueryDepth
6
- def result
7
- configured_max_depth = if query
8
- query.max_depth
9
- else
10
- multiplex.schema.max_depth
11
- end
12
-
13
- if configured_max_depth && @max_depth > configured_max_depth
14
- GraphQL::AnalysisError.new("Query has depth of #{@max_depth}, which exceeds max depth of #{configured_max_depth}")
15
- else
16
- nil
17
- end
18
- end
19
- end
20
- end
21
- end
22
- end
@@ -1,185 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- # Calculate the complexity of a query, using {Field#complexity} values.
5
- module AST
6
- class QueryComplexity < Analyzer
7
- # State for the query complexity calculation:
8
- # - `complexities_on_type` holds complexity scores for each type
9
- def initialize(query)
10
- super
11
- @skip_introspection_fields = !query.schema.max_complexity_count_introspection_fields
12
- @complexities_on_type_by_query = {}
13
- end
14
-
15
- # Override this method to use the complexity result
16
- def result
17
- max_possible_complexity
18
- end
19
-
20
- # ScopedTypeComplexity models a tree of GraphQL types mapped to inner selections, ie:
21
- # Hash<GraphQL::BaseType, Hash<String, ScopedTypeComplexity>>
22
- class ScopedTypeComplexity < Hash
23
- # A proc for defaulting empty namespace requests as a new scope hash.
24
- DEFAULT_PROC = ->(h, k) { h[k] = {} }
25
-
26
- attr_reader :field_definition, :response_path, :query
27
-
28
- # @param parent_type [Class] The owner of `field_definition`
29
- # @param field_definition [GraphQL::Field, GraphQL::Schema::Field] Used for getting the `.complexity` configuration
30
- # @param query [GraphQL::Query] Used for `query.possible_types`
31
- # @param response_path [Array<String>] The path to the response key for the field
32
- # @return [Hash<GraphQL::BaseType, Hash<String, ScopedTypeComplexity>>]
33
- def initialize(parent_type, field_definition, query, response_path)
34
- super(&DEFAULT_PROC)
35
- @parent_type = parent_type
36
- @field_definition = field_definition
37
- @query = query
38
- @response_path = response_path
39
- @nodes = []
40
- end
41
-
42
- # @return [Array<GraphQL::Language::Nodes::Field>]
43
- attr_reader :nodes
44
-
45
- def own_complexity(child_complexity)
46
- @field_definition.calculate_complexity(query: @query, nodes: @nodes, child_complexity: child_complexity)
47
- end
48
- end
49
-
50
- def on_enter_field(node, parent, visitor)
51
- # We don't want to visit fragment definitions,
52
- # we'll visit them when we hit the spreads instead
53
- return if visitor.visiting_fragment_definition?
54
- return if visitor.skipping?
55
- return if @skip_introspection_fields && visitor.field_definition.introspection?
56
- parent_type = visitor.parent_type_definition
57
- field_key = node.alias || node.name
58
-
59
- # Find or create a complexity scope stack for this query.
60
- scopes_stack = @complexities_on_type_by_query[visitor.query] ||= [ScopedTypeComplexity.new(nil, nil, query, visitor.response_path)]
61
-
62
- # Find or create the complexity costing node for this field.
63
- scope = scopes_stack.last[parent_type][field_key] ||= ScopedTypeComplexity.new(parent_type, visitor.field_definition, visitor.query, visitor.response_path)
64
- scope.nodes.push(node)
65
- scopes_stack.push(scope)
66
- end
67
-
68
- def on_leave_field(node, parent, visitor)
69
- # We don't want to visit fragment definitions,
70
- # we'll visit them when we hit the spreads instead
71
- return if visitor.visiting_fragment_definition?
72
- return if visitor.skipping?
73
- return if @skip_introspection_fields && visitor.field_definition.introspection?
74
- scopes_stack = @complexities_on_type_by_query[visitor.query]
75
- scopes_stack.pop
76
- end
77
-
78
- private
79
-
80
- # @return [Integer]
81
- def max_possible_complexity
82
- @complexities_on_type_by_query.reduce(0) do |total, (query, scopes_stack)|
83
- total + merged_max_complexity_for_scopes(query, [scopes_stack.first])
84
- end
85
- end
86
-
87
- # @param query [GraphQL::Query] Used for `query.possible_types`
88
- # @param scopes [Array<ScopedTypeComplexity>] Array of scoped type complexities
89
- # @return [Integer]
90
- def merged_max_complexity_for_scopes(query, scopes)
91
- # Aggregate a set of all possible scope types encountered (scope keys).
92
- # Use a hash, but ignore the values; it's just a fast way to work with the keys.
93
- possible_scope_types = scopes.each_with_object({}) do |scope, memo|
94
- memo.merge!(scope)
95
- end
96
-
97
- # Expand abstract scope types into their concrete implementations;
98
- # overlapping abstracts coalesce through their intersecting types.
99
- possible_scope_types.keys.each do |possible_scope_type|
100
- next unless possible_scope_type.kind.abstract?
101
-
102
- query.possible_types(possible_scope_type).each do |impl_type|
103
- possible_scope_types[impl_type] ||= true
104
- end
105
- possible_scope_types.delete(possible_scope_type)
106
- end
107
-
108
- # Aggregate the lexical selections that may apply to each possible type,
109
- # and then return the maximum cost among possible typed selections.
110
- possible_scope_types.each_key.reduce(0) do |max, possible_scope_type|
111
- # Collect inner selections from all scopes that intersect with this possible type.
112
- all_inner_selections = scopes.each_with_object([]) do |scope, memo|
113
- scope.each do |scope_type, inner_selections|
114
- memo << inner_selections if types_intersect?(query, scope_type, possible_scope_type)
115
- end
116
- end
117
-
118
- # Find the maximum complexity for the scope type among possible lexical branches.
119
- complexity = merged_max_complexity(query, all_inner_selections)
120
- complexity > max ? complexity : max
121
- end
122
- end
123
-
124
- def types_intersect?(query, a, b)
125
- return true if a == b
126
-
127
- a_types = query.possible_types(a)
128
- query.possible_types(b).any? { |t| a_types.include?(t) }
129
- end
130
-
131
- # A hook which is called whenever a field's max complexity is calculated.
132
- # Override this method to capture individual field complexity details.
133
- #
134
- # @param scoped_type_complexity [ScopedTypeComplexity]
135
- # @param max_complexity [Numeric] Field's maximum complexity including child complexity
136
- # @param child_complexity [Numeric, nil] Field's child complexity
137
- def field_complexity(scoped_type_complexity, max_complexity:, child_complexity: nil)
138
- end
139
-
140
- # @param inner_selections [Array<Hash<String, ScopedTypeComplexity>>] Field selections for a scope
141
- # @return [Integer] Total complexity value for all these selections in the parent scope
142
- def merged_max_complexity(query, inner_selections)
143
- # Aggregate a set of all unique field selection keys across all scopes.
144
- # Use a hash, but ignore the values; it's just a fast way to work with the keys.
145
- unique_field_keys = inner_selections.each_with_object({}) do |inner_selection, memo|
146
- memo.merge!(inner_selection)
147
- end
148
-
149
- # Add up the total cost for each unique field name's coalesced selections
150
- unique_field_keys.each_key.reduce(0) do |total, field_key|
151
- composite_scopes = nil
152
- field_cost = 0
153
-
154
- # Collect composite selection scopes for further aggregation,
155
- # leaf selections report their costs directly.
156
- inner_selections.each do |inner_selection|
157
- child_scope = inner_selection[field_key]
158
- next unless child_scope
159
-
160
- # Empty child scopes are leaf nodes with zero child complexity.
161
- if child_scope.empty?
162
- field_cost = child_scope.own_complexity(0)
163
- field_complexity(child_scope, max_complexity: field_cost, child_complexity: nil)
164
- else
165
- composite_scopes ||= []
166
- composite_scopes << child_scope
167
- end
168
- end
169
-
170
- if composite_scopes
171
- child_complexity = merged_max_complexity_for_scopes(query, composite_scopes)
172
-
173
- # This is the last composite scope visited; assume it's representative (for backwards compatibility).
174
- # Note: it would be more correct to score each composite scope and use the maximum possibility.
175
- field_cost = composite_scopes.last.own_complexity(child_complexity)
176
- field_complexity(composite_scopes.last, max_complexity: field_cost, child_complexity: child_complexity)
177
- end
178
-
179
- total + field_cost
180
- end
181
- end
182
- end
183
- end
184
- end
185
- end
@@ -1,284 +0,0 @@
1
- # frozen_string_literal: true
2
- module GraphQL
3
- module Analysis
4
- module AST
5
- # Depth first traversal through a query AST, calling AST analyzers
6
- # along the way.
7
- #
8
- # The visitor is a special case of GraphQL::Language::StaticVisitor, visiting
9
- # only the selected operation, providing helpers for common use cases such
10
- # as skipped fields and visiting fragment spreads.
11
- #
12
- # @see {GraphQL::Analysis::AST::Analyzer} AST Analyzers for queries
13
- class Visitor < GraphQL::Language::StaticVisitor
14
- def initialize(query:, analyzers:)
15
- @analyzers = analyzers
16
- @path = []
17
- @object_types = []
18
- @directives = []
19
- @field_definitions = []
20
- @argument_definitions = []
21
- @directive_definitions = []
22
- @rescued_errors = []
23
- @query = query
24
- @schema = query.schema
25
- @response_path = []
26
- @skip_stack = [false]
27
- super(query.selected_operation)
28
- end
29
-
30
- # @return [GraphQL::Query] the query being visited
31
- attr_reader :query
32
-
33
- # @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
34
- attr_reader :object_types
35
-
36
- # @return [Array<GraphQL::AnalysisError]
37
- attr_reader :rescued_errors
38
-
39
- def visit
40
- return unless @document
41
- super
42
- end
43
-
44
- # Visit Helpers
45
-
46
- # @return [GraphQL::Execution::Interpreter::Arguments] Arguments for this node, merging default values, literal values and query variables
47
- # @see {GraphQL::Query#arguments_for}
48
- def arguments_for(ast_node, field_definition)
49
- @query.arguments_for(ast_node, field_definition)
50
- end
51
-
52
- # @return [Boolean] If the visitor is currently inside a fragment definition
53
- def visiting_fragment_definition?
54
- @in_fragment_def
55
- end
56
-
57
- # @return [Boolean] If the current node should be skipped because of a skip or include directive
58
- def skipping?
59
- @skipping
60
- end
61
-
62
- # @return [Array<String>] The path to the response key for the current field
63
- def response_path
64
- @response_path.dup
65
- end
66
-
67
- # Visitor Hooks
68
- [
69
- :operation_definition, :fragment_definition,
70
- :inline_fragment, :field, :directive, :argument, :fragment_spread
71
- ].each do |node_type|
72
- module_eval <<-RUBY, __FILE__, __LINE__
73
- def call_on_enter_#{node_type}(node, parent)
74
- @analyzers.each do |a|
75
- begin
76
- a.on_enter_#{node_type}(node, parent, self)
77
- rescue AnalysisError => err
78
- @rescued_errors << err
79
- end
80
- end
81
- end
82
-
83
- def call_on_leave_#{node_type}(node, parent)
84
- @analyzers.each do |a|
85
- begin
86
- a.on_leave_#{node_type}(node, parent, self)
87
- rescue AnalysisError => err
88
- @rescued_errors << err
89
- end
90
- end
91
- end
92
-
93
- RUBY
94
- end
95
-
96
- def on_operation_definition(node, parent)
97
- object_type = @schema.root_type_for_operation(node.operation_type)
98
- @object_types.push(object_type)
99
- @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
100
- call_on_enter_operation_definition(node, parent)
101
- super
102
- call_on_leave_operation_definition(node, parent)
103
- @object_types.pop
104
- @path.pop
105
- end
106
-
107
- def on_fragment_definition(node, parent)
108
- on_fragment_with_type(node) do
109
- @path.push("fragment #{node.name}")
110
- @in_fragment_def = false
111
- call_on_enter_fragment_definition(node, parent)
112
- super
113
- @in_fragment_def = false
114
- call_on_leave_fragment_definition(node, parent)
115
- end
116
- end
117
-
118
- def on_inline_fragment(node, parent)
119
- on_fragment_with_type(node) do
120
- @path.push("...#{node.type ? " on #{node.type.name}" : ""}")
121
- @skipping = @skip_stack.last || skip?(node)
122
- @skip_stack << @skipping
123
-
124
- call_on_enter_inline_fragment(node, parent)
125
- super
126
- @skipping = @skip_stack.pop
127
- call_on_leave_inline_fragment(node, parent)
128
- end
129
- end
130
-
131
- def on_field(node, parent)
132
- @response_path.push(node.alias || node.name)
133
- parent_type = @object_types.last
134
- # This could be nil if the previous field wasn't found:
135
- field_definition = parent_type && @schema.get_field(parent_type, node.name, @query.context)
136
- @field_definitions.push(field_definition)
137
- if !field_definition.nil?
138
- next_object_type = field_definition.type.unwrap
139
- @object_types.push(next_object_type)
140
- else
141
- @object_types.push(nil)
142
- end
143
- @path.push(node.alias || node.name)
144
-
145
- @skipping = @skip_stack.last || skip?(node)
146
- @skip_stack << @skipping
147
-
148
- call_on_enter_field(node, parent)
149
- super
150
- @skipping = @skip_stack.pop
151
- call_on_leave_field(node, parent)
152
- @response_path.pop
153
- @field_definitions.pop
154
- @object_types.pop
155
- @path.pop
156
- end
157
-
158
- def on_directive(node, parent)
159
- directive_defn = @schema.directives[node.name]
160
- @directive_definitions.push(directive_defn)
161
- call_on_enter_directive(node, parent)
162
- super
163
- call_on_leave_directive(node, parent)
164
- @directive_definitions.pop
165
- end
166
-
167
- def on_argument(node, parent)
168
- argument_defn = if (arg = @argument_definitions.last)
169
- arg_type = arg.type.unwrap
170
- if arg_type.kind.input_object?
171
- arg_type.get_argument(node.name, @query.context)
172
- else
173
- nil
174
- end
175
- elsif (directive_defn = @directive_definitions.last)
176
- directive_defn.get_argument(node.name, @query.context)
177
- elsif (field_defn = @field_definitions.last)
178
- field_defn.get_argument(node.name, @query.context)
179
- else
180
- nil
181
- end
182
-
183
- @argument_definitions.push(argument_defn)
184
- @path.push(node.name)
185
- call_on_enter_argument(node, parent)
186
- super
187
- call_on_leave_argument(node, parent)
188
- @argument_definitions.pop
189
- @path.pop
190
- end
191
-
192
- def on_fragment_spread(node, parent)
193
- @path.push("... #{node.name}")
194
- @skipping = @skip_stack.last || skip?(node)
195
- @skip_stack << @skipping
196
-
197
- call_on_enter_fragment_spread(node, parent)
198
- enter_fragment_spread_inline(node)
199
- super
200
- @skipping = @skip_stack.pop
201
- leave_fragment_spread_inline(node)
202
- call_on_leave_fragment_spread(node, parent)
203
- @path.pop
204
- end
205
-
206
- # @return [GraphQL::BaseType] The current object type
207
- def type_definition
208
- @object_types.last
209
- end
210
-
211
- # @return [GraphQL::BaseType] The type which the current type came from
212
- def parent_type_definition
213
- @object_types[-2]
214
- end
215
-
216
- # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
217
- def field_definition
218
- @field_definitions.last
219
- end
220
-
221
- # @return [GraphQL::Field, nil] The GraphQL field which returned the object that the current field belongs to
222
- def previous_field_definition
223
- @field_definitions[-2]
224
- end
225
-
226
- # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
227
- def directive_definition
228
- @directive_definitions.last
229
- end
230
-
231
- # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
232
- def argument_definition
233
- @argument_definitions.last
234
- end
235
-
236
- # @return [GraphQL::Argument, nil] The previous GraphQL argument
237
- def previous_argument_definition
238
- @argument_definitions[-2]
239
- end
240
-
241
- private
242
-
243
- # Visit a fragment spread inline instead of visiting the definition
244
- # by itself.
245
- def enter_fragment_spread_inline(fragment_spread)
246
- fragment_def = query.fragments[fragment_spread.name]
247
-
248
- object_type = if fragment_def.type
249
- @query.warden.get_type(fragment_def.type.name)
250
- else
251
- object_types.last
252
- end
253
-
254
- object_types << object_type
255
-
256
- on_fragment_definition_children(fragment_def)
257
- end
258
-
259
- # Visit a fragment spread inline instead of visiting the definition
260
- # by itself.
261
- def leave_fragment_spread_inline(_fragment_spread)
262
- object_types.pop
263
- end
264
-
265
- def skip?(ast_node)
266
- dir = ast_node.directives
267
- dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
268
- end
269
-
270
- def on_fragment_with_type(node)
271
- object_type = if node.type
272
- @query.warden.get_type(node.type.name)
273
- else
274
- @object_types.last
275
- end
276
- @object_types.push(object_type)
277
- yield(node)
278
- @object_types.pop
279
- @path.pop
280
- end
281
- end
282
- end
283
- end
284
- end
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
- require "graphql/analysis/ast/visitor"
3
- require "graphql/analysis/ast/analyzer"
4
- require "graphql/analysis/ast/field_usage"
5
- require "graphql/analysis/ast/query_complexity"
6
- require "graphql/analysis/ast/max_query_complexity"
7
- require "graphql/analysis/ast/query_depth"
8
- require "graphql/analysis/ast/max_query_depth"
9
- require "timeout"
10
-
11
- module GraphQL
12
- module Analysis
13
- module AST
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::AST::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::AST::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::AST::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
94
- end