graphql 0.15.3 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/graphql.rb +4 -1
- data/lib/graphql/analysis.rb +5 -0
- data/lib/graphql/analysis/analyze_query.rb +73 -0
- data/lib/graphql/analysis/max_query_complexity.rb +25 -0
- data/lib/graphql/analysis/max_query_depth.rb +25 -0
- data/lib/graphql/analysis/query_complexity.rb +122 -0
- data/lib/graphql/analysis/query_depth.rb +54 -0
- data/lib/graphql/analysis_error.rb +4 -0
- data/lib/graphql/base_type.rb +7 -0
- data/lib/graphql/define/assign_object_field.rb +2 -1
- data/lib/graphql/field.rb +25 -3
- data/lib/graphql/input_object_type.rb +1 -1
- data/lib/graphql/internal_representation.rb +2 -0
- data/lib/graphql/internal_representation/node.rb +81 -0
- data/lib/graphql/internal_representation/rewrite.rb +177 -0
- data/lib/graphql/language/visitor.rb +15 -9
- data/lib/graphql/object_type.rb +1 -1
- data/lib/graphql/query.rb +66 -7
- data/lib/graphql/query/context.rb +10 -3
- data/lib/graphql/query/directive_resolution.rb +5 -5
- data/lib/graphql/query/serial_execution.rb +5 -3
- data/lib/graphql/query/serial_execution/field_resolution.rb +22 -15
- data/lib/graphql/query/serial_execution/operation_resolution.rb +7 -5
- data/lib/graphql/query/serial_execution/selection_resolution.rb +20 -105
- data/lib/graphql/query/serial_execution/value_resolution.rb +15 -12
- data/lib/graphql/schema.rb +7 -2
- data/lib/graphql/schema/timeout_middleware.rb +67 -0
- data/lib/graphql/static_validation/all_rules.rb +0 -1
- data/lib/graphql/static_validation/type_stack.rb +7 -11
- data/lib/graphql/static_validation/validation_context.rb +11 -1
- data/lib/graphql/static_validation/validator.rb +14 -4
- data/lib/graphql/version.rb +1 -1
- data/readme.md +10 -9
- data/spec/graphql/analysis/analyze_query_spec.rb +50 -0
- data/spec/graphql/analysis/max_query_complexity_spec.rb +62 -0
- data/spec/graphql/{static_validation/rules/document_does_not_exceed_max_depth_spec.rb → analysis/max_query_depth_spec.rb} +20 -21
- data/spec/graphql/analysis/query_complexity_spec.rb +235 -0
- data/spec/graphql/analysis/query_depth_spec.rb +80 -0
- data/spec/graphql/directive_spec.rb +1 -0
- data/spec/graphql/internal_representation/rewrite_spec.rb +120 -0
- data/spec/graphql/introspection/schema_type_spec.rb +1 -0
- data/spec/graphql/language/visitor_spec.rb +14 -4
- data/spec/graphql/non_null_type_spec.rb +31 -0
- data/spec/graphql/query/context_spec.rb +24 -1
- data/spec/graphql/query_spec.rb +6 -2
- data/spec/graphql/schema/timeout_middleware_spec.rb +180 -0
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/arguments_are_defined_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/directives_are_defined_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/directives_are_in_valid_locations_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fields_are_defined_on_type_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fields_have_appropriate_selections_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragment_spreads_are_possible_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragment_types_exist_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragments_are_on_composite_types_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/required_arguments_are_present_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/variable_default_values_are_correctly_typed_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/variable_usages_are_allowed_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/variables_are_used_and_defined_spec.rb +1 -1
- data/spec/graphql/static_validation/validator_spec.rb +1 -1
- data/spec/support/dairy_app.rb +22 -1
- metadata +29 -5
- data/lib/graphql/static_validation/rules/document_does_not_exceed_max_depth.rb +0 -79
@@ -0,0 +1,81 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
module GraphQL
|
4
|
+
module InternalRepresentation
|
5
|
+
class Node
|
6
|
+
def initialize(ast_node:, return_type: nil, on_types: Set.new, name: nil, definition: nil, children: {}, spreads: [], directives: Set.new)
|
7
|
+
@ast_node = ast_node
|
8
|
+
@return_type = return_type
|
9
|
+
@on_types = on_types
|
10
|
+
@name = name
|
11
|
+
@definition = definition
|
12
|
+
@children = children
|
13
|
+
@spreads = spreads
|
14
|
+
@directives = directives
|
15
|
+
end
|
16
|
+
|
17
|
+
# Note: by the time this gets out of the Rewrite phase, this will be empty -- it's emptied out when fragments are merged back in
|
18
|
+
# @return [Array<GraphQL::Language::Nodes::FragmentSpreads>] Fragment names that were spread in this node
|
19
|
+
attr_reader :spreads
|
20
|
+
|
21
|
+
# These are the compiled directives from fragment spreads, inline fragments, and the field itself
|
22
|
+
# @return [Set<GraphQL::Language::Nodes::Directive>]
|
23
|
+
attr_reader :directives
|
24
|
+
|
25
|
+
# @return [GraphQL::Field, GraphQL::Directive] The definition to use to execute this node
|
26
|
+
attr_reader :definition
|
27
|
+
|
28
|
+
# @return [String] the name to use for the result in the response hash
|
29
|
+
attr_reader :name
|
30
|
+
|
31
|
+
# @return [GraphQL::Language::Nodes::AbstractNode] The AST node (or one of the nodes) where this was derived from
|
32
|
+
attr_reader :ast_node
|
33
|
+
|
34
|
+
# This may come from the previous field's return value or an explicitly-typed fragment
|
35
|
+
# @example On-type from previous return value
|
36
|
+
# {
|
37
|
+
# person(id: 1) {
|
38
|
+
# firstName # => on_type is person
|
39
|
+
# }
|
40
|
+
# }
|
41
|
+
# @example On-type from explicit type condition
|
42
|
+
# {
|
43
|
+
# node(id: $nodeId) {
|
44
|
+
# ... on Nameable {
|
45
|
+
# firstName # => on_type is Nameable
|
46
|
+
# }
|
47
|
+
# }
|
48
|
+
# }
|
49
|
+
# @return [Set<GraphQL::ObjectType, GraphQL::InterfaceType>] the types this field applies to
|
50
|
+
attr_reader :on_types
|
51
|
+
|
52
|
+
# @return [GraphQL::BaseType]
|
53
|
+
attr_reader :return_type
|
54
|
+
|
55
|
+
# @return [Array<GraphQL::Query::Node>]
|
56
|
+
attr_reader :children
|
57
|
+
|
58
|
+
def inspect(indent = 0)
|
59
|
+
own_indent = " " * indent
|
60
|
+
self_inspect = "#{own_indent}<Node #{name} (#{definition ? definition.name + ": " : ""}{#{on_types.to_a.join("|")}} -> #{return_type})>"
|
61
|
+
if children.any?
|
62
|
+
self_inspect << " {\n#{children.values.map { |n| n.inspect(indent + 2 )}.join("\n")}\n#{own_indent}}"
|
63
|
+
end
|
64
|
+
self_inspect
|
65
|
+
end
|
66
|
+
|
67
|
+
def dup
|
68
|
+
self.class.new({
|
69
|
+
ast_node: ast_node,
|
70
|
+
return_type: return_type,
|
71
|
+
on_types: on_types,
|
72
|
+
name: name,
|
73
|
+
definition: definition,
|
74
|
+
children: children,
|
75
|
+
spreads: spreads,
|
76
|
+
directives: directives,
|
77
|
+
})
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
module GraphQL
|
2
|
+
module InternalRepresentation
|
3
|
+
# Convert an AST into a tree of {InternalRepresentation::Node}s
|
4
|
+
#
|
5
|
+
# This rides along with {StaticValidation}, building a tree of nodes.
|
6
|
+
#
|
7
|
+
# However, if any errors occurred during validation, the resulting tree is bogus.
|
8
|
+
# (For example, `nil` could have been pushed instead of a type.)
|
9
|
+
class Rewrite
|
10
|
+
include GraphQL::Language
|
11
|
+
|
12
|
+
# @return [Hash<String => InternalRepresentation::Node>] internal representation of each query root (operation, fragment)
|
13
|
+
attr_reader :operations
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
# { String => Node } Tracks the roots of the query
|
17
|
+
@operations = {}
|
18
|
+
@fragments = {}
|
19
|
+
# [String...] fragments which don't have fragments inside them
|
20
|
+
@independent_fragments = []
|
21
|
+
# Tracks the current node during traversal
|
22
|
+
# Stack<InternalRepresentation::Node>
|
23
|
+
@nodes = []
|
24
|
+
# This tracks dependencies from fragment to Node where it was used
|
25
|
+
# { frag_name => [dependent_node, dependent_node]}
|
26
|
+
@fragment_spreads = Hash.new { |h, k| h[k] = []}
|
27
|
+
# [Nodes::Directive ... ] directive affecting the current scope
|
28
|
+
@parent_directives = []
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate(context)
|
32
|
+
visitor = context.visitor
|
33
|
+
|
34
|
+
visitor[Nodes::OperationDefinition].enter << -> (ast_node, prev_ast_node) {
|
35
|
+
node = Node.new(
|
36
|
+
return_type: context.type_definition.unwrap,
|
37
|
+
ast_node: ast_node,
|
38
|
+
)
|
39
|
+
@nodes.push(node)
|
40
|
+
@operations[ast_node.name] = node
|
41
|
+
}
|
42
|
+
|
43
|
+
visitor[Nodes::Field].enter << -> (ast_node, prev_ast_node) {
|
44
|
+
parent_node = @nodes.last
|
45
|
+
node_name = ast_node.alias || ast_node.name
|
46
|
+
# This node might not be novel, eg inside an inline fragment
|
47
|
+
# but it could contain new type information, which is captured below.
|
48
|
+
# (StaticValidation ensures that merging fields is fair game)
|
49
|
+
node = parent_node.children[node_name] ||= begin
|
50
|
+
Node.new(
|
51
|
+
return_type: context.type_definition && context.type_definition.unwrap,
|
52
|
+
ast_node: ast_node,
|
53
|
+
name: node_name,
|
54
|
+
definition: context.field_definition,
|
55
|
+
)
|
56
|
+
end
|
57
|
+
node.on_types.add(context.parent_type_definition.unwrap)
|
58
|
+
@nodes.push(node)
|
59
|
+
@parent_directives.push([])
|
60
|
+
}
|
61
|
+
|
62
|
+
visitor[Nodes::InlineFragment].enter << -> (ast_node, prev_ast_node) {
|
63
|
+
@parent_directives.push([])
|
64
|
+
}
|
65
|
+
|
66
|
+
visitor[Nodes::Directive].enter << -> (ast_node, prev_ast_node) {
|
67
|
+
# It could be a query error where a directive is somewhere it shouldn't be
|
68
|
+
if @parent_directives.any?
|
69
|
+
@parent_directives.last << Node.new(
|
70
|
+
name: ast_node.name,
|
71
|
+
ast_node: ast_node,
|
72
|
+
definition: context.directive_definition,
|
73
|
+
)
|
74
|
+
end
|
75
|
+
}
|
76
|
+
|
77
|
+
visitor[Nodes::FragmentSpread].enter << -> (ast_node, prev_ast_node) {
|
78
|
+
# Record _both sides_ of the dependency
|
79
|
+
spread_node = Node.new(
|
80
|
+
name: ast_node.name,
|
81
|
+
ast_node: ast_node,
|
82
|
+
)
|
83
|
+
parent_node = @nodes.last
|
84
|
+
# The parent node has a reference to the fragment
|
85
|
+
parent_node.spreads.push(spread_node)
|
86
|
+
# And keep a reference from the fragment to the parent node
|
87
|
+
@fragment_spreads[ast_node.name].push(parent_node)
|
88
|
+
@nodes.push(spread_node)
|
89
|
+
@parent_directives.push([])
|
90
|
+
}
|
91
|
+
|
92
|
+
visitor[Nodes::FragmentDefinition].enter << -> (ast_node, prev_ast_node) {
|
93
|
+
node = Node.new(
|
94
|
+
name: ast_node.name,
|
95
|
+
return_type: context.type_definition,
|
96
|
+
ast_node: ast_node,
|
97
|
+
)
|
98
|
+
@nodes.push(node)
|
99
|
+
@fragments[ast_node.name] = node
|
100
|
+
}
|
101
|
+
|
102
|
+
visitor[Nodes::InlineFragment].leave << -> (ast_node, prev_ast_node) {
|
103
|
+
@parent_directives.pop
|
104
|
+
}
|
105
|
+
|
106
|
+
visitor[Nodes::FragmentSpread].leave << -> (ast_node, prev_ast_node) {
|
107
|
+
# Capture any directives that apply to this spread
|
108
|
+
# so that they can be applied to fields when
|
109
|
+
# the fragment is merged in later
|
110
|
+
spread_node = @nodes.pop
|
111
|
+
spread_node.directives.merge(@parent_directives.flatten)
|
112
|
+
@parent_directives.pop
|
113
|
+
}
|
114
|
+
|
115
|
+
visitor[Nodes::FragmentDefinition].leave << -> (ast_node, prev_ast_node) {
|
116
|
+
# This fragment doesn't depend on any others,
|
117
|
+
# we should save it as the starting point for dependency resolution
|
118
|
+
frag_node = @nodes.pop
|
119
|
+
if frag_node.spreads.none?
|
120
|
+
@independent_fragments << frag_node
|
121
|
+
end
|
122
|
+
}
|
123
|
+
|
124
|
+
visitor[Nodes::OperationDefinition].leave << -> (ast_node, prev_ast_node) {
|
125
|
+
@nodes.pop
|
126
|
+
}
|
127
|
+
|
128
|
+
visitor[Nodes::Field].leave << -> (ast_node, prev_ast_node) {
|
129
|
+
# Pop this field's node
|
130
|
+
# and record any directives that were visited
|
131
|
+
# during this field & before it (eg, inline fragments)
|
132
|
+
field_node = @nodes.pop
|
133
|
+
field_node.directives.merge(@parent_directives.flatten)
|
134
|
+
@parent_directives.pop
|
135
|
+
}
|
136
|
+
|
137
|
+
visitor[Nodes::Document].leave << -> (ast_node, prev_ast_node) {
|
138
|
+
# Resolve fragment dependencies. Start with fragments with no
|
139
|
+
# dependencies and work along the spreads.
|
140
|
+
while fragment_node = @independent_fragments.pop
|
141
|
+
fragment_usages = @fragment_spreads[fragment_node.name]
|
142
|
+
while dependent_node = fragment_usages.pop
|
143
|
+
# remove self from dependent_node.spreads
|
144
|
+
rejected_spread_nodes = dependent_node.spreads.select { |spr| spr.name == fragment_node.name }
|
145
|
+
rejected_spread_nodes.each { |r_node| dependent_node.spreads.delete(r_node) }
|
146
|
+
|
147
|
+
# resolve the dependency (merge into dependent node)
|
148
|
+
deep_merge(dependent_node, fragment_node, rejected_spread_nodes.first.directives)
|
149
|
+
|
150
|
+
if dependent_node.spreads.none? && dependent_node.ast_node.is_a?(Nodes::FragmentDefinition)
|
151
|
+
@independent_fragments.push(dependent_node)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
# Merge the chilren from `fragment_node` into `parent_node`. Merge `directives` into each of those fields.
|
161
|
+
def deep_merge(parent_node, fragment_node, directives)
|
162
|
+
fragment_node.children.each do |name, child_node|
|
163
|
+
deep_merge_child(parent_node, name, child_node, directives)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Merge `node` into `parent_node`'s children, as `name`, applying `extra_directives`
|
168
|
+
def deep_merge_child(parent_node, name, node, extra_directives)
|
169
|
+
child_node = parent_node.children[name] ||= node.dup
|
170
|
+
node.children.each do |merge_child_name, merge_child_node|
|
171
|
+
deep_merge_child(child_node, merge_child_name, merge_child_node, [])
|
172
|
+
end
|
173
|
+
child_node.directives.merge(extra_directives)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -4,12 +4,12 @@ module GraphQL
|
|
4
4
|
#
|
5
5
|
# @example Create a visitor, add hooks, then search a document
|
6
6
|
# total_field_count = 0
|
7
|
-
# visitor = GraphQL::Language::Visitor.new
|
7
|
+
# visitor = GraphQL::Language::Visitor.new(document)
|
8
8
|
# # Whenever you find a field, increment the field count:
|
9
9
|
# visitor[GraphQL::Language::Nodes::Field] << -> (node) { total_field_count += 1 }
|
10
10
|
# # When we finish, print the field count:
|
11
11
|
# visitor[GraphQL::Language::Nodes::Document].leave << -> (node) { p total_field_count }
|
12
|
-
# visitor.visit
|
12
|
+
# visitor.visit
|
13
13
|
# # => 6
|
14
14
|
#
|
15
15
|
class Visitor
|
@@ -22,7 +22,8 @@ module GraphQL
|
|
22
22
|
# @return [Array<Proc>] Hooks to call when leaving _any_ node
|
23
23
|
attr_reader :leave
|
24
24
|
|
25
|
-
def initialize
|
25
|
+
def initialize(document)
|
26
|
+
@document = document
|
26
27
|
@visitors = {}
|
27
28
|
@enter = []
|
28
29
|
@leave = []
|
@@ -38,17 +39,22 @@ module GraphQL
|
|
38
39
|
@visitors[node_class] ||= NodeVisitor.new
|
39
40
|
end
|
40
41
|
|
41
|
-
# Visit `
|
42
|
-
# @param root [GraphQL::Language::Nodes::AbstractNode] some node to start parsing on
|
42
|
+
# Visit `document` and all children, applying hooks as you go
|
43
43
|
# @return [void]
|
44
|
-
def visit
|
45
|
-
|
46
|
-
root.children.reduce(true) { |memo, child| memo && visit(child, root) }
|
47
|
-
end_visit(root, parent)
|
44
|
+
def visit
|
45
|
+
visit_node(@document, nil)
|
48
46
|
end
|
49
47
|
|
50
48
|
private
|
51
49
|
|
50
|
+
def visit_node(node, parent)
|
51
|
+
begin_hooks_ok = begin_visit(node, parent)
|
52
|
+
if begin_hooks_ok
|
53
|
+
node.children.reduce(true) { |memo, child| memo && visit_node(child, node) }
|
54
|
+
end
|
55
|
+
end_visit(node, parent)
|
56
|
+
end
|
57
|
+
|
52
58
|
def begin_visit(node, parent)
|
53
59
|
self.class.apply_hooks(enter, node, parent)
|
54
60
|
node_visitor = self[node.class]
|
data/lib/graphql/object_type.rb
CHANGED
@@ -24,7 +24,7 @@ module GraphQL
|
|
24
24
|
accepts_definitions :interfaces, field: GraphQL::Define::AssignObjectField
|
25
25
|
attr_accessor :name, :description
|
26
26
|
|
27
|
-
# @return [Hash<String
|
27
|
+
# @return [Hash<String => GraphQL::Field>] Map String fieldnames to their {GraphQL::Field} implementations
|
28
28
|
attr_accessor :fields
|
29
29
|
|
30
30
|
def initialize
|
data/lib/graphql/query.rb
CHANGED
@@ -8,7 +8,7 @@ module GraphQL
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
attr_reader :schema, :document, :context, :fragments, :operations, :root_value, :max_depth
|
11
|
+
attr_reader :schema, :document, :context, :fragments, :operations, :root_value, :max_depth, :query_string
|
12
12
|
|
13
13
|
# Prepare query `query_string` on `schema`
|
14
14
|
# @param schema [GraphQL::Schema]
|
@@ -18,11 +18,21 @@ module GraphQL
|
|
18
18
|
# @param validate [Boolean] if true, `query_string` will be validated with {StaticValidation::Validator}
|
19
19
|
# @param operation_name [String] if the query string contains many operations, this is the one which should be executed
|
20
20
|
# @param root_value [Object] the object used to resolve fields on the root type
|
21
|
-
|
21
|
+
# @param max_depth [Numeric] the maximum number of nested selections allowed for this query (falls back to schema-level value)
|
22
|
+
# @param max_complexity [Numeric] the maximum field complexity for this query (falls back to schema-level value)
|
23
|
+
def initialize(schema, query_string = nil, document: nil, context: nil, variables: {}, validate: true, operation_name: nil, root_value: nil, max_depth: nil, max_complexity: nil)
|
22
24
|
fail ArgumentError, "a query string or document is required" unless query_string || document
|
23
25
|
|
24
26
|
@schema = schema
|
25
27
|
@max_depth = max_depth || schema.max_depth
|
28
|
+
@max_complexity = max_complexity || schema.max_complexity
|
29
|
+
@query_analyzers = schema.query_analyzers.dup
|
30
|
+
if @max_depth
|
31
|
+
@query_analyzers << GraphQL::Analysis::MaxQueryDepth.new(@max_depth)
|
32
|
+
end
|
33
|
+
if @max_complexity
|
34
|
+
@query_analyzers << GraphQL::Analysis::MaxQueryComplexity.new(@max_complexity)
|
35
|
+
end
|
26
36
|
@context = Context.new(query: self, values: context)
|
27
37
|
@root_value = root_value
|
28
38
|
@validate = validate
|
@@ -30,6 +40,7 @@ module GraphQL
|
|
30
40
|
@fragments = {}
|
31
41
|
@operations = {}
|
32
42
|
@provided_variables = variables
|
43
|
+
@query_string = query_string
|
33
44
|
|
34
45
|
@document = document || GraphQL.parse(query_string)
|
35
46
|
@document.definitions.each do |part|
|
@@ -39,15 +50,20 @@ module GraphQL
|
|
39
50
|
@operations[part.name] = part
|
40
51
|
end
|
41
52
|
end
|
53
|
+
|
54
|
+
@arguments_cache = {}
|
42
55
|
end
|
43
56
|
|
44
57
|
# Get the result for this query, executing it once
|
45
58
|
def result
|
46
|
-
|
47
|
-
|
59
|
+
@result ||= begin
|
60
|
+
if @validate && (validation_errors.any? || analysis_errors.any?)
|
61
|
+
{ "errors" => validation_errors + analysis_errors}
|
62
|
+
else
|
63
|
+
Executor.new(self).result
|
64
|
+
end
|
48
65
|
end
|
49
66
|
|
50
|
-
@result ||= Executor.new(self).result
|
51
67
|
end
|
52
68
|
|
53
69
|
|
@@ -71,10 +87,39 @@ module GraphQL
|
|
71
87
|
)
|
72
88
|
end
|
73
89
|
|
74
|
-
|
90
|
+
def internal_representation
|
91
|
+
@internal_representation ||= begin
|
92
|
+
perform_validation
|
93
|
+
@internal_representation
|
94
|
+
end
|
95
|
+
end
|
75
96
|
|
76
97
|
def validation_errors
|
77
|
-
@validation_errors ||=
|
98
|
+
@validation_errors ||= begin
|
99
|
+
perform_validation
|
100
|
+
@validation_errors
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Node-level cache for calculating arguments. Used during execution and query analysis.
|
105
|
+
# @return [GraphQL::Query::Arguments] Arguments for this node, merging default values, literal values and query variables
|
106
|
+
def arguments_for(irep_node)
|
107
|
+
@arguments_cache[irep_node] ||= begin
|
108
|
+
GraphQL::Query::LiteralInput.from_arguments(
|
109
|
+
irep_node.ast_node.arguments,
|
110
|
+
irep_node.definition.arguments,
|
111
|
+
self.variables
|
112
|
+
)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def perform_validation
|
119
|
+
validation_result = schema.static_validator.validate(self)
|
120
|
+
@validation_errors = validation_result[:errors]
|
121
|
+
@internal_representation = validation_result[:irep]
|
122
|
+
nil
|
78
123
|
end
|
79
124
|
|
80
125
|
|
@@ -89,6 +134,20 @@ module GraphQL
|
|
89
134
|
operations[operation_name]
|
90
135
|
end
|
91
136
|
end
|
137
|
+
|
138
|
+
def analysis_errors
|
139
|
+
@analysis_errors ||= begin
|
140
|
+
if validation_errors.any?
|
141
|
+
# Can't reduce an invalid query
|
142
|
+
[]
|
143
|
+
elsif @query_analyzers.any?
|
144
|
+
reduce_results = GraphQL::Analysis.analyze_query(self, @query_analyzers)
|
145
|
+
reduce_results.select { |r| r.is_a?(GraphQL::AnalysisError) }.map(&:to_h)
|
146
|
+
else
|
147
|
+
[]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
92
151
|
end
|
93
152
|
end
|
94
153
|
|